1 /* mate-about-me.c
2 * Copyright (C) 2002 Diego Gonzalez
3 * Copyright (C) 2006 Johannes H. Jensen
4 *
5 * Written by: Diego Gonzalez <diego@pemas.net>
6 * Modified by: Johannes H. Jensen <joh@deworks.net>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2, or (at your option)
11 * any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 * 02110-1301, USA.
22 *
23 * Parts of this code come from Mate-System-Tools.
24 */
25
26 #ifdef HAVE_CONFIG_H
27 # include <config.h>
28 #endif
29
30 /* Are all of these needed? */
31 #include <gdk/gdkkeysyms.h>
32 #include <pwd.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <string.h>
37 #include <sys/wait.h>
38
39 #ifdef __sun
40 #include <sys/types.h>
41 #include <signal.h>
42 #endif
43
44 #include "capplet-util.h"
45 #include "mate-about-me-password.h"
46
47 /* Passwd states */
48 typedef enum {
49 PASSWD_STATE_NONE, /* Passwd is not asking for anything */
50 PASSWD_STATE_AUTH, /* Passwd is asking for our current password */
51 PASSWD_STATE_NEW, /* Passwd is asking for our new password */
52 PASSWD_STATE_RETYPE, /* Passwd is asking for our retyped new password */
53 PASSWD_STATE_ERR /* Passwd reported an error but has not yet exited */
54 } PasswdState;
55
56 typedef struct {
57 GtkBuilder *ui;
58
59 /* Commonly used widgets */
60 GtkEntry *current_password;
61 GtkEntry *new_password;
62 GtkEntry *retyped_password;
63 GtkImage *dialog_image;
64 GtkLabel *status_label;
65
66 /* Whether we have authenticated */
67 gboolean authenticated;
68
69 /* Communication with the passwd program */
70 GPid backend_pid;
71
72 GIOChannel *backend_stdin;
73 GIOChannel *backend_stdout;
74
75 GQueue *backend_stdin_queue; /* Write queue to backend_stdin */
76
77 /* GMainLoop IDs */
78 guint backend_child_watch_id; /* g_child_watch_add (PID) */
79 guint backend_stdout_watch_id; /* g_io_add_watch (stdout) */
80
81 /* State of the passwd program */
82 PasswdState backend_state;
83
84 } PasswordDialog;
85
86 /* Buffer size for backend output */
87 #define BUFSIZE 64
88
89 /*
90 * Error handling {{
91 */
92 #define PASSDLG_ERROR (mate_about_me_password_error_quark())
93
mate_about_me_password_error_quark(void)94 static GQuark mate_about_me_password_error_quark(void)
95 {
96 static GQuark q = 0;
97
98 if (q == 0) {
99 q = g_quark_from_static_string("mate_about_me_password_error");
100 }
101
102 return q;
103 }
104
105 /* error codes */
106 enum {
107 PASSDLG_ERROR_NONE,
108 PASSDLG_ERROR_NEW_PASSWORD_EMPTY,
109 PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY,
110 PASSDLG_ERROR_PASSWORDS_NOT_EQUAL,
111 PASSDLG_ERROR_BACKEND, /* Backend error */
112 PASSDLG_ERROR_USER, /* Generic user error */
113 PASSDLG_ERROR_FAILED /* Fatal failure, error->message should explain */
114 };
115
116 /*
117 * }} Error handling
118 */
119
120 /*
121 * Prototypes {{
122 */
123 static void
124 stop_passwd (PasswordDialog *pdialog);
125
126 static void
127 free_passwd_resources (PasswordDialog *pdialog);
128
129 static gboolean
130 io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswordDialog *pdialog);
131
132 static void
133 passdlg_set_auth_state (PasswordDialog *pdialog, gboolean state);
134
135 static void
136 passdlg_set_status (PasswordDialog *pdialog, gchar *msg);
137
138 static void
139 passdlg_set_busy (PasswordDialog *pdialog, gboolean busy);
140
141 static void
142 passdlg_clear (PasswordDialog *pdialog);
143
144 static guint
145 passdlg_refresh_password_state (PasswordDialog *pdialog);
146
147 /*
148 * }} Prototypes
149 */
150
151 /*
152 * Spawning and closing of backend {{
153 */
154
155 /* Child watcher */
156 static void
child_watch_cb(GPid pid,gint status,PasswordDialog * pdialog)157 child_watch_cb (GPid pid, gint status, PasswordDialog *pdialog)
158 {
159 if (WIFEXITED (status)) {
160 if (WEXITSTATUS (status) >= 255) {
161 g_warning (_("Child exited unexpectedly"));
162 }
163 }
164
165 free_passwd_resources (pdialog);
166 }
167
168 /* Spawn passwd backend
169 * Returns: TRUE on success, FALSE otherwise and sets error appropriately */
170 static gboolean
spawn_passwd(PasswordDialog * pdialog,GError ** error)171 spawn_passwd (PasswordDialog *pdialog, GError **error)
172 {
173 gchar *argv[2];
174 gchar *envp[1];
175 gint my_stdin, my_stdout, my_stderr;
176
177 argv[0] = "/usr/bin/passwd"; /* Is it safe to rely on a hard-coded path? */
178 argv[1] = NULL;
179
180 envp[0] = NULL; /* If we pass an empty array as the environment,
181 * will the childs environment be empty, and the
182 * locales set to the C default? From the manual:
183 * "If envp is NULL, the child inherits its
184 * parent'senvironment."
185 * If I'm wrong here, we somehow have to set
186 * the locales here.
187 */
188
189 if (!g_spawn_async_with_pipes (NULL, /* Working directory */
190 argv, /* Argument vector */
191 envp, /* Environment */
192 G_SPAWN_DO_NOT_REAP_CHILD, /* Flags */
193 NULL, /* Child setup */
194 NULL, /* Data to child setup */
195 &pdialog->backend_pid, /* PID */
196 &my_stdin, /* Stdin */
197 &my_stdout, /* Stdout */
198 &my_stderr, /* Stderr */
199 error)) { /* GError */
200
201 /* An error occurred */
202 free_passwd_resources (pdialog);
203
204 return FALSE;
205 }
206
207 /* 2>&1 */
208 if (dup2 (my_stderr, my_stdout) == -1) {
209 /* Failed! */
210 g_set_error (error,
211 PASSDLG_ERROR,
212 PASSDLG_ERROR_BACKEND,
213 "%s",
214 strerror (errno));
215
216 /* Clean up */
217 stop_passwd (pdialog);
218
219 return FALSE;
220 }
221
222 /* Open IO Channels */
223 pdialog->backend_stdin = g_io_channel_unix_new (my_stdin);
224 pdialog->backend_stdout = g_io_channel_unix_new (my_stdout);
225
226 /* Set raw encoding */
227 /* Set nonblocking mode */
228 if (g_io_channel_set_encoding (pdialog->backend_stdin, NULL, error) != G_IO_STATUS_NORMAL ||
229 g_io_channel_set_encoding (pdialog->backend_stdout, NULL, error) != G_IO_STATUS_NORMAL ||
230 g_io_channel_set_flags (pdialog->backend_stdin, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ||
231 g_io_channel_set_flags (pdialog->backend_stdout, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ) {
232
233 /* Clean up */
234 stop_passwd (pdialog);
235 return FALSE;
236 }
237
238 /* Turn off buffering */
239 g_io_channel_set_buffered (pdialog->backend_stdin, FALSE);
240 g_io_channel_set_buffered (pdialog->backend_stdout, FALSE);
241
242 /* Add IO Channel watcher */
243 pdialog->backend_stdout_watch_id = g_io_add_watch (pdialog->backend_stdout,
244 G_IO_IN | G_IO_PRI,
245 (GIOFunc) io_watch_stdout, pdialog);
246
247 /* Add child watcher */
248 pdialog->backend_child_watch_id = g_child_watch_add (pdialog->backend_pid, (GChildWatchFunc) child_watch_cb, pdialog);
249
250 /* Success! */
251
252 return TRUE;
253 }
254
255 /* Stop passwd backend */
256 static void
stop_passwd(PasswordDialog * pdialog)257 stop_passwd (PasswordDialog *pdialog)
258 {
259 /* This is the standard way of returning from the dialog with passwd.
260 * If we return this way we can safely kill passwd as it has completed
261 * its task.
262 */
263
264 if (pdialog->backend_pid != -1) {
265 kill (pdialog->backend_pid, 9);
266 }
267
268 /* We must run free_passwd_resources here and not let our child
269 * watcher do it, since it will access invalid memory after the
270 * dialog has been closed and cleaned up.
271 *
272 * If we had more than a single thread we'd need to remove
273 * the child watch before trying to kill the child.
274 */
275 free_passwd_resources (pdialog);
276 }
277
278 /* Clean up passwd resources */
279 static void
free_passwd_resources(PasswordDialog * pdialog)280 free_passwd_resources (PasswordDialog *pdialog)
281 {
282 GError *error = NULL;
283
284 /* Remove the child watcher */
285 if (pdialog->backend_child_watch_id != 0) {
286
287 g_source_remove (pdialog->backend_child_watch_id);
288
289 pdialog->backend_child_watch_id = 0;
290 }
291
292
293 /* Close IO channels (internal file descriptors are automatically closed) */
294 if (pdialog->backend_stdin != NULL) {
295
296 if (g_io_channel_shutdown (pdialog->backend_stdin, TRUE, &error) != G_IO_STATUS_NORMAL) {
297 g_warning (_("Could not shutdown backend_stdin IO channel: %s"), error->message);
298 g_error_free (error);
299 error = NULL;
300 }
301
302 g_io_channel_unref (pdialog->backend_stdin);
303
304 pdialog->backend_stdin = NULL;
305 }
306
307 if (pdialog->backend_stdout != NULL) {
308
309 if (g_io_channel_shutdown (pdialog->backend_stdout, TRUE, &error) != G_IO_STATUS_NORMAL) {
310 g_warning (_("Could not shutdown backend_stdout IO channel: %s"), error->message);
311 g_error_free (error);
312 error = NULL;
313 }
314
315 g_io_channel_unref (pdialog->backend_stdout);
316
317 pdialog->backend_stdout = NULL;
318 }
319
320 /* Remove IO watcher */
321 if (pdialog->backend_stdout_watch_id != 0) {
322
323 g_source_remove (pdialog->backend_stdout_watch_id);
324
325 pdialog->backend_stdout_watch_id = 0;
326 }
327
328 /* Close PID */
329 if (pdialog->backend_pid != -1) {
330
331 g_spawn_close_pid (pdialog->backend_pid);
332
333 pdialog->backend_pid = -1;
334 }
335
336 /* Clear backend state */
337 pdialog->backend_state = PASSWD_STATE_NONE;
338 }
339
340 /*
341 * }} Spawning and closing of backend
342 */
343
344 /*
345 * Backend communication code {{
346 */
347
348 /* Write the first element of queue through channel */
349 static void
io_queue_pop(GQueue * queue,GIOChannel * channel)350 io_queue_pop (GQueue *queue, GIOChannel *channel)
351 {
352 gchar *buf;
353 gsize bytes_written;
354 GError *error = NULL;
355
356 buf = g_queue_pop_head (queue);
357
358 if (buf != NULL) {
359
360 if (g_io_channel_write_chars (channel, buf, -1, &bytes_written, &error) != G_IO_STATUS_NORMAL) {
361 g_warning ("Could not write queue element \"%s\" to channel: %s", buf, error->message);
362 g_error_free (error);
363 }
364
365 g_free (buf);
366 }
367 }
368
369 /* Goes through the argument list, checking if one of them occurs in str
370 * Returns: TRUE as soon as an element is found to match, FALSE otherwise */
371 static gboolean
is_string_complete(gchar * str,...)372 is_string_complete (gchar *str, ...)
373 {
374 va_list ap;
375 gchar *arg;
376
377 if (strlen (str) == 0) {
378 return FALSE;
379 }
380
381 va_start (ap, str);
382
383 while ((arg = va_arg (ap, char *)) != NULL) {
384 if (g_strrstr (str, arg) != NULL) {
385 va_end (ap);
386 return TRUE;
387 }
388 }
389
390 va_end (ap);
391
392 return FALSE;
393 }
394
395 /* Authentication attempt succeeded. Update the GUI accordingly. */
396 static void
authenticated_user(PasswordDialog * pdialog)397 authenticated_user (PasswordDialog *pdialog)
398 {
399 pdialog->backend_state = PASSWD_STATE_NEW;
400
401 if (pdialog->authenticated) {
402 /* This is a re-authentication
403 * It succeeded, so pop our new password from the queue */
404 io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin);
405 }
406
407 /* Update UI state */
408 passdlg_set_auth_state (pdialog, TRUE);
409 passdlg_set_status (pdialog, _("Authenticated!"));
410
411 /* Check to see if the passwords are valid
412 * (They might be non-empty if the user had to re-authenticate,
413 * and thus we need to enable the change-password-button) */
414 passdlg_refresh_password_state (pdialog);
415 }
416
417 /*
418 * IO watcher for stdout, called whenever there is data to read from the backend.
419 * This is where most of the actual IO handling happens.
420 */
421 static gboolean
io_watch_stdout(GIOChannel * source,GIOCondition condition,PasswordDialog * pdialog)422 io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswordDialog *pdialog)
423 {
424 static GString *str = NULL; /* Persistent buffer */
425
426 gchar buf[BUFSIZE]; /* Temporary buffer */
427 gsize bytes_read;
428 GError *error = NULL;
429
430 gchar *msg = NULL; /* Status error message */
431
432 gboolean reinit = FALSE;
433
434 /* Initialize buffer */
435 if (str == NULL) {
436 str = g_string_new ("");
437 }
438
439 if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &error) != G_IO_STATUS_NORMAL) {
440 if (error) {
441 g_warning ("IO Channel read error: %s", error->message);
442 g_error_free (error);
443 }
444
445 return TRUE;
446 }
447
448 str = g_string_append_len (str, buf, bytes_read);
449
450 /* In which state is the backend? */
451 switch (pdialog->backend_state) {
452 case PASSWD_STATE_AUTH:
453 /* Passwd is asking for our current password */
454
455 if (is_string_complete (str->str, "assword: ", "failure", "wrong", "error", NULL)) {
456 /* Which response did we get? */
457 passdlg_set_busy (pdialog, FALSE);
458
459 if (g_strrstr (str->str, "assword: ") != NULL) {
460 /* Authentication successful */
461
462 authenticated_user (pdialog);
463
464 } else {
465 /* Authentication failed */
466
467 if (pdialog->authenticated) {
468 /* This is a re-auth, and it failed.
469 * The password must have been changed in the meantime!
470 * Ask the user to re-authenticate
471 */
472
473 /* Update status message and auth state */
474 passdlg_set_status (pdialog, _("Your password has been changed since you initially authenticated! Please re-authenticate."));
475 } else {
476 passdlg_set_status (pdialog, _("That password was incorrect."));
477 }
478
479 /* Focus current password */
480 gtk_widget_grab_focus (GTK_WIDGET (pdialog->current_password));
481 }
482
483 reinit = TRUE;
484 }
485 break;
486 case PASSWD_STATE_NEW:
487 /* Passwd is asking for our new password */
488
489 if (is_string_complete (str->str, "assword: ", NULL)) {
490 /* Advance to next state */
491 pdialog->backend_state = PASSWD_STATE_RETYPE;
492
493 /* Pop retyped password from queue and into IO channel */
494 io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin);
495
496 reinit = TRUE;
497 }
498 break;
499 case PASSWD_STATE_RETYPE:
500 /* Passwd is asking for our retyped new password */
501
502 if (is_string_complete (str->str, "successfully",
503 "short",
504 "longer",
505 "palindrome",
506 "dictionary",
507 "simpl", /* catches both simple and simplistic */
508 "similar",
509 "different",
510 "case",
511 "wrapped",
512 "recovered",
513 "recent",
514 "unchanged",
515 "match",
516 "1 numeric or special",
517 "failure",
518 NULL)) {
519
520 /* What response did we get? */
521 passdlg_set_busy (pdialog, FALSE);
522
523 if (g_strrstr (str->str, "successfully") != NULL) {
524 /* Hooray! */
525
526 passdlg_clear (pdialog);
527 passdlg_set_status (pdialog, _("Your password has been changed."));
528 } else {
529 /* Ohnoes! */
530
531 /* Focus new password */
532 gtk_widget_grab_focus (GTK_WIDGET (pdialog->new_password));
533
534 if (g_strrstr (str->str, "recovered") != NULL) {
535 /* What does this indicate?
536 * "Authentication information cannot be recovered?" from libpam? */
537 msg = g_strdup_printf (_("System error: %s."), str->str);
538 } else if (g_strrstr (str->str, "short") != NULL ||
539 g_strrstr (str->str, "longer") != NULL) {
540 msg = g_strdup (_("The password is too short."));
541 } else if (g_strrstr (str->str, "palindrome") != NULL ||
542 g_strrstr (str->str, "simpl") != NULL ||
543 g_strrstr (str->str, "dictionary") != NULL) {
544 msg = g_strdup (_("The password is too simple."));
545 } else if (g_strrstr (str->str, "similar") != NULL ||
546 g_strrstr (str->str, "different") != NULL ||
547 g_strrstr (str->str, "case") != NULL ||
548 g_strrstr (str->str, "wrapped") != NULL) {
549 msg = g_strdup (_("The old and new passwords are too similar."));
550 } else if (g_strrstr (str->str, "1 numeric or special") != NULL) {
551 msg = g_strdup (_("The new password must contain numeric or special character(s)."));
552 } else if (g_strrstr (str->str, "unchanged") != NULL ||
553 g_strrstr (str->str, "match") != NULL) {
554 msg = g_strdup (_("The old and new passwords are the same."));
555 } else if (g_strrstr (str->str, "recent") != NULL) {
556 msg = g_strdup (_("The new password has already been used recently."));
557 } else if (g_strrstr (str->str, "failure") != NULL) {
558 /* Authentication failure */
559 msg = g_strdup (_("Your password has been changed since you initially authenticated! Please re-authenticate."));
560
561 passdlg_set_auth_state (pdialog, FALSE);
562 }
563 }
564
565 reinit = TRUE;
566
567 if (msg != NULL) {
568 /* An error occurred! */
569 passdlg_set_status (pdialog, msg);
570 g_free (msg);
571
572 /* At this point, passwd might have exited, in which case
573 * child_watch_cb should clean up for us and remove this watcher.
574 * On some error conditions though, passwd just re-prompts us
575 * for our new password. */
576
577 pdialog->backend_state = PASSWD_STATE_ERR;
578 }
579
580 /* child_watch_cb should clean up for us now */
581 }
582 break;
583 case PASSWD_STATE_NONE:
584 /* Passwd is not asking for anything yet */
585 if (is_string_complete (str->str, "assword: ", NULL)) {
586
587 /* If the user does not have a password set,
588 * passwd will immediately ask for the new password,
589 * so skip the AUTH phase */
590 if (is_string_complete (str->str, "new", "New", NULL)) {
591 gchar *pw;
592
593 pdialog->backend_state = PASSWD_STATE_NEW;
594
595 passdlg_set_busy (pdialog, FALSE);
596 authenticated_user (pdialog);
597
598 /* since passwd didn't ask for our old password
599 * in this case, simply remove it from the queue */
600 pw = g_queue_pop_head (pdialog->backend_stdin_queue);
601 g_free (pw);
602 } else {
603
604 pdialog->backend_state = PASSWD_STATE_AUTH;
605
606 /* Pop the IO queue, i.e. send current password */
607 io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin);
608 }
609
610 reinit = TRUE;
611 }
612 break;
613 default:
614 /* Passwd has returned an error */
615 reinit = TRUE;
616 break;
617 }
618
619 if (reinit) {
620 g_string_free (str, TRUE);
621 str = NULL;
622 }
623
624 /* Continue calling us */
625 return TRUE;
626 }
627
628 /*
629 * }} Backend communication code
630 */
631
632 /* Adds the current password to the IO queue */
633 static void
authenticate(PasswordDialog * pdialog)634 authenticate (PasswordDialog *pdialog)
635 {
636 gchar *s;
637
638 s = g_strdup_printf ("%s\n", gtk_entry_get_text (pdialog->current_password));
639
640 g_queue_push_tail (pdialog->backend_stdin_queue, s);
641 }
642
643 /* Adds the new password twice to the IO queue */
644 static void
update_password(PasswordDialog * pdialog)645 update_password (PasswordDialog *pdialog)
646 {
647 gchar *s;
648
649 s = g_strdup_printf ("%s\n", gtk_entry_get_text (pdialog->new_password));
650
651 g_queue_push_tail (pdialog->backend_stdin_queue, s);
652 /* We need to allocate new space because io_queue_pop() g_free()s
653 * every element of the queue after it's done */
654 g_queue_push_tail (pdialog->backend_stdin_queue, g_strdup (s));
655 }
656
657 /* Sets dialog busy state according to busy
658 *
659 * When busy:
660 * Sets the cursor to busy
661 * Disables the interface to prevent that the user interferes
662 * Reverts all this when non-busy
663 *
664 * Note that this function takes into account the
665 * authentication state of the dialog. So setting the
666 * dialog to busy and then back to normal should leave
667 * the dialog unchanged.
668 */
669 static void
passdlg_set_busy(PasswordDialog * pdialog,gboolean busy)670 passdlg_set_busy (PasswordDialog *pdialog, gboolean busy)
671 {
672 GtkBuilder *dialog;
673 GtkWidget *toplevel;
674 GdkCursor *cursor = NULL;
675 GdkDisplay *display;
676
677 dialog = pdialog->ui;
678
679 /* Set cursor */
680 toplevel = WID ("change-password");
681 display = gtk_widget_get_display (toplevel);
682 if (busy) {
683 cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
684 }
685
686 gdk_window_set_cursor (gtk_widget_get_window (toplevel), cursor);
687 gdk_display_flush (display);
688
689 if (busy) {
690 g_object_unref (cursor);
691 }
692
693 /* Disable/Enable UI */
694 if (pdialog->authenticated) {
695 /* Authenticated state */
696
697 /* Enable/disable new password section */
698 g_object_set (pdialog->new_password, "sensitive", !busy, NULL);
699 g_object_set (pdialog->retyped_password, "sensitive", !busy, NULL);
700 g_object_set (WID ("new-password-label"), "sensitive", !busy, NULL);
701 g_object_set (WID ("retyped-password-label"), "sensitive", !busy, NULL);
702
703 /* Enable/disable change password button */
704 g_object_set (WID ("change-password-button"), "sensitive", !busy, NULL);
705
706 } else {
707 /* Not-authenticated state */
708
709 /* Enable/disable auth section state */
710 g_object_set (pdialog->current_password, "sensitive", !busy, NULL);
711 g_object_set (WID ("authenticate-button"), "sensitive", !busy, NULL);
712 g_object_set (WID ("current-password-label"), "sensitive", !busy, NULL);
713 }
714 }
715
716 /* Launch an error dialog */
717 static void
passdlg_error_dialog(GtkWindow * parent,const gchar * title,const gchar * msg,const gchar * details)718 passdlg_error_dialog (GtkWindow *parent, const gchar *title,
719 const gchar *msg, const gchar *details)
720 {
721 GtkWidget *dialog;
722
723 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
724 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
725 "%s", msg);
726 if (title)
727 gtk_window_set_title (GTK_WINDOW (dialog), title);
728
729 if (details)
730 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
731 "%s", details);
732 gtk_dialog_run (GTK_DIALOG (dialog));
733 gtk_widget_destroy (dialog);
734 }
735
736 /* Set authenticated state of dialog according to state
737 *
738 * When in authenticated state:
739 * Disables authentication-part of interface
740 * Enables new-password-part of interface
741 * When in not-authenticated state:
742 * Enables authentication-part of interface
743 * Disables new-password-part of interface
744 * Disables the change-password-button
745 */
746 static void
passdlg_set_auth_state(PasswordDialog * pdialog,gboolean state)747 passdlg_set_auth_state (PasswordDialog *pdialog, gboolean state)
748 {
749 GtkBuilder *dialog;
750
751 dialog = pdialog->ui;
752
753 /* Widgets which require a not-authenticated state to be accessible */
754 g_object_set (pdialog->current_password, "sensitive", !state, NULL);
755 g_object_set (WID ("current-password-label"), "sensitive", !state, NULL);
756 g_object_set (WID ("authenticate-button"), "sensitive", !state, NULL);
757
758 /* Widgets which require authentication to be accessible */
759 g_object_set (pdialog->new_password, "sensitive", state, NULL);
760 g_object_set (pdialog->retyped_password, "sensitive", state, NULL);
761 g_object_set (WID ("new-password-label"), "sensitive", state, NULL);
762 g_object_set (WID ("retyped-password-label"), "sensitive", state, NULL);
763
764 if (!state) {
765 /* Disable change-password-button when in not-authenticated state */
766 g_object_set (WID ("change-password-button"), "sensitive", FALSE, NULL);
767 }
768
769 pdialog->authenticated = state;
770
771 if (state) {
772 /* Authenticated state */
773
774 /* Focus new password */
775 gtk_widget_grab_focus (GTK_WIDGET (pdialog->new_password));
776
777 /* Set open lock image */
778 gtk_image_set_from_icon_name (GTK_IMAGE (pdialog->dialog_image), "changes-allow", GTK_ICON_SIZE_DIALOG);
779 } else {
780 /* Not authenticated state */
781
782 /* Focus current password */
783 gtk_widget_grab_focus (GTK_WIDGET (pdialog->current_password));
784
785 /* Set closed lock image */
786 gtk_image_set_from_icon_name (GTK_IMAGE (pdialog->dialog_image), "changes-prevent", GTK_ICON_SIZE_DIALOG);
787 }
788 }
789
790 /* Set status field message */
791 static void
passdlg_set_status(PasswordDialog * pdialog,gchar * msg)792 passdlg_set_status (PasswordDialog *pdialog, gchar *msg)
793 {
794 g_object_set (pdialog->status_label, "label", msg, NULL);
795 }
796
797 /* Clear dialog (except the status message) */
798 static void
passdlg_clear(PasswordDialog * pdialog)799 passdlg_clear (PasswordDialog *pdialog)
800 {
801 /* Set non-authenticated state */
802 passdlg_set_auth_state (pdialog, FALSE);
803
804 /* Clear password entries */
805 gtk_entry_set_text (pdialog->current_password, "");
806 gtk_entry_set_text (pdialog->new_password, "");
807 gtk_entry_set_text (pdialog->retyped_password, "");
808 }
809
810 /* Start backend and handle errors
811 * If backend is already running, stop it
812 * If an error occurs, show error dialog */
813 static gboolean
passdlg_spawn_passwd(PasswordDialog * pdialog)814 passdlg_spawn_passwd (PasswordDialog *pdialog)
815 {
816 GError *error = NULL;
817 gchar *details;
818
819 /* If backend is running, stop it */
820 stop_passwd (pdialog);
821
822 /* Spawn backend */
823 if (!spawn_passwd (pdialog, &error)) {
824 GtkWidget *parent = GTK_WIDGET (gtk_builder_get_object (pdialog->ui, "change-password"));
825
826 /* translators: Unable to launch <program>: <error message> */
827 details = g_strdup_printf (_("Unable to launch %s: %s"),
828 "/usr/bin/passwd", error->message);
829
830 passdlg_error_dialog (GTK_WINDOW (parent),
831 _("Unable to launch backend"),
832 _("A system error has occurred"),
833 details);
834
835 g_free (details);
836 g_error_free (error);
837
838 return FALSE;
839 }
840
841 return TRUE;
842 }
843
844 /* Called when the "Authenticate" button is clicked */
845 static void
passdlg_authenticate(GtkButton * button,PasswordDialog * pdialog)846 passdlg_authenticate (GtkButton *button, PasswordDialog *pdialog)
847 {
848 /* Set busy as this can be a long process */
849 passdlg_set_busy (pdialog, TRUE);
850
851 /* Update status message */
852 passdlg_set_status (pdialog, _("Checking password..."));
853
854 /* Spawn backend */
855 if (!passdlg_spawn_passwd (pdialog)) {
856 passdlg_set_busy (pdialog, FALSE);
857 return;
858 }
859
860 authenticate (pdialog);
861
862 /* Our IO watcher should now handle the rest */
863 }
864
865 /* Validate passwords
866 * Returns:
867 * PASSDLG_ERROR_NONE (0) if passwords are valid
868 * PASSDLG_ERROR_NEW_PASSWORD_EMPTY
869 * PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY
870 * PASSDLG_ERROR_PASSWORDS_NOT_EQUAL
871 */
872 static guint
passdlg_validate_passwords(PasswordDialog * pdialog)873 passdlg_validate_passwords (PasswordDialog *pdialog)
874 {
875 const gchar *new_password, *retyped_password;
876 glong nlen, rlen;
877
878 new_password = gtk_entry_get_text (pdialog->new_password);
879 retyped_password = gtk_entry_get_text (pdialog->retyped_password);
880
881 nlen = g_utf8_strlen (new_password, -1);
882 rlen = g_utf8_strlen (retyped_password, -1);
883
884 if (nlen == 0) {
885 /* New password empty */
886 return PASSDLG_ERROR_NEW_PASSWORD_EMPTY;
887 }
888
889 if (rlen == 0) {
890 /* Retyped password empty */
891 return PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY;
892 }
893
894 if (nlen != rlen || strncmp (new_password, retyped_password, nlen) != 0) {
895 /* Passwords not equal */
896 return PASSDLG_ERROR_PASSWORDS_NOT_EQUAL;
897 }
898
899 /* Success */
900 return PASSDLG_ERROR_NONE;
901 }
902
903 /* Refresh the valid password UI state, i.e. re-validate
904 * and enable/disable the Change Password button.
905 * Returns: Return value of passdlg_validate_passwords */
906 static guint
passdlg_refresh_password_state(PasswordDialog * pdialog)907 passdlg_refresh_password_state (PasswordDialog *pdialog)
908 {
909 GtkBuilder *dialog;
910 guint ret;
911 gboolean valid = FALSE;
912
913 dialog = pdialog->ui;
914
915 ret = passdlg_validate_passwords (pdialog);
916
917 if (ret == PASSDLG_ERROR_NONE) {
918 valid = TRUE;
919 }
920
921 g_object_set (WID ("change-password-button"), "sensitive", valid, NULL);
922
923 return ret;
924 }
925
926 /* Called whenever any of the new password fields have changed */
927 static void
passdlg_check_password(GtkEntry * entry,PasswordDialog * pdialog)928 passdlg_check_password (GtkEntry *entry, PasswordDialog *pdialog)
929 {
930 guint ret;
931
932 ret = passdlg_refresh_password_state (pdialog);
933
934 switch (ret) {
935 case PASSDLG_ERROR_NONE:
936 passdlg_set_status (pdialog, _("Click <b>Change password</b> to change your password."));
937 break;
938 case PASSDLG_ERROR_NEW_PASSWORD_EMPTY:
939 passdlg_set_status (pdialog, _("Please type your password in the <b>New password</b> field."));
940 break;
941 case PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY:
942 passdlg_set_status (pdialog, _("Please type your password again in the <b>Retype new password</b> field."));
943 break;
944 case PASSDLG_ERROR_PASSWORDS_NOT_EQUAL:
945 passdlg_set_status (pdialog, _("The two passwords are not equal."));
946 break;
947 default:
948 g_warning ("Unknown passdlg_check_password error: %d", ret);
949 break;
950 }
951 }
952
953 /* Called when the "Change password" dialog-button is clicked
954 * Returns: TRUE if we want to keep the dialog running, FALSE otherwise */
955 static gboolean
passdlg_process_response(PasswordDialog * pdialog,gint response_id)956 passdlg_process_response (PasswordDialog *pdialog, gint response_id)
957 {
958
959 if (response_id == GTK_RESPONSE_OK) {
960 /* Set busy as this can be a long process */
961 passdlg_set_busy (pdialog, TRUE);
962
963 /* Stop passwd if an error occurred and it is still running */
964 if (pdialog->backend_state == PASSWD_STATE_ERR) {
965
966 /* Stop passwd, free resources */
967 stop_passwd (pdialog);
968 }
969
970 /* Check that the backend is still running, or that an error
971 * has occurred but it has not yet exited */
972 if (pdialog->backend_pid == -1) {
973 /* If it is not, re-run authentication */
974
975 /* Spawn backend */
976 if (!passdlg_spawn_passwd (pdialog)) {
977 return TRUE;
978 }
979
980 /* Add current and new passwords to queue */
981 authenticate (pdialog);
982 update_password (pdialog);
983 } else {
984 /* Only add new passwords to queue */
985 update_password (pdialog);
986
987 /* Pop new password through the backend */
988 io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin);
989 }
990
991 /* Our IO watcher should now handle the rest */
992
993 /* Keep the dialog running */
994 return TRUE;
995 }
996
997 return FALSE;
998 }
999
1000 /* Activates (moves focus or activates) widget w */
1001 static void
passdlg_activate(GtkEntry * entry,GtkWidget * w)1002 passdlg_activate (GtkEntry *entry, GtkWidget *w)
1003 {
1004 if (GTK_IS_BUTTON (w)) {
1005 gtk_widget_activate (w);
1006 } else {
1007 gtk_widget_grab_focus (w);
1008 }
1009 }
1010
1011 /* Initialize password dialog */
1012 static void
passdlg_init(PasswordDialog * pdialog,GtkWindow * parent)1013 passdlg_init (PasswordDialog *pdialog, GtkWindow *parent)
1014 {
1015 GtkBuilder *dialog;
1016 GtkWidget *wpassdlg;
1017 GtkAccelGroup *group;
1018 GError *error = NULL;
1019
1020 /* Initialize dialog */
1021 dialog = gtk_builder_new ();
1022 if (gtk_builder_add_from_resource (dialog, "/org/mate/mcc/am/mate-about-me-password.ui", &error) == 0)
1023 {
1024 g_warning ("Could not parse UI definition: %s", error->message);
1025 g_error_free (error);
1026 }
1027 pdialog->ui = dialog;
1028
1029 wpassdlg = WID ("change-password");
1030 capplet_set_icon (wpassdlg, "user-info");
1031
1032 group = gtk_accel_group_new ();
1033
1034 /*
1035 * Initialize backend
1036 */
1037
1038 /* Initialize backend_pid. -1 means the backend is not running */
1039 pdialog->backend_pid = -1;
1040
1041 /* Initialize IO Channels */
1042 pdialog->backend_stdin = NULL;
1043 pdialog->backend_stdout = NULL;
1044
1045 /* Initialize write queue */
1046 pdialog->backend_stdin_queue = g_queue_new ();
1047
1048 /* Initialize watchers */
1049 pdialog->backend_child_watch_id = 0;
1050 pdialog->backend_stdout_watch_id = 0;
1051
1052 /* Initialize backend state */
1053 pdialog->backend_state = PASSWD_STATE_NONE;
1054
1055 /*
1056 * Initialize UI
1057 */
1058
1059 /* Initialize pdialog widgets */
1060 pdialog->current_password = GTK_ENTRY (WID ("current-password"));
1061 pdialog->new_password = GTK_ENTRY (WID ("new-password"));
1062 pdialog->retyped_password = GTK_ENTRY (WID ("retyped-password"));
1063 pdialog->dialog_image = GTK_IMAGE (WID ("dialog-image"));
1064 pdialog->status_label = GTK_LABEL (WID ("status-label"));
1065
1066 /* Initialize accelerators */
1067 gtk_widget_add_accelerator (GTK_WIDGET (pdialog->current_password),
1068 "activate", group,
1069 GDK_KEY_Return, 0,
1070 0);
1071
1072 gtk_widget_add_accelerator (GTK_WIDGET (pdialog->new_password),
1073 "activate", group,
1074 GDK_KEY_Return, 0,
1075 0);
1076
1077 /* Activate authenticate-button when enter is pressed in current-password */
1078 g_signal_connect (G_OBJECT (pdialog->current_password), "activate",
1079 G_CALLBACK (passdlg_activate), WID ("authenticate-button"));
1080
1081 /* Activate retyped-password when enter is pressed in new-password */
1082 g_signal_connect (G_OBJECT (pdialog->new_password), "activate",
1083 G_CALLBACK (passdlg_activate), pdialog->retyped_password);
1084
1085 /* Clear status message */
1086 passdlg_set_status (pdialog, "");
1087
1088 /* Set non-authenticated state */
1089 passdlg_set_auth_state (pdialog, FALSE);
1090
1091 /* Connect signal handlers */
1092 g_signal_connect (G_OBJECT (WID ("authenticate-button")), "clicked",
1093 G_CALLBACK (passdlg_authenticate), pdialog);
1094
1095 /* Verify new passwords on-the-fly */
1096 g_signal_connect (G_OBJECT (WID ("new-password")), "changed",
1097 G_CALLBACK (passdlg_check_password), pdialog);
1098 g_signal_connect (G_OBJECT (WID ("retyped-password")), "changed",
1099 G_CALLBACK (passdlg_check_password), pdialog);
1100
1101 /* Set misc dialog properties */
1102 gtk_window_set_resizable (GTK_WINDOW (wpassdlg), FALSE);
1103 gtk_window_set_transient_for (GTK_WINDOW (wpassdlg), GTK_WINDOW (parent));
1104 }
1105
1106 /* Main */
1107 void
mate_about_me_password(GtkWindow * parent)1108 mate_about_me_password (GtkWindow *parent)
1109 {
1110 PasswordDialog *pdialog;
1111 GtkBuilder *dialog;
1112 GtkWidget *wpassdlg;
1113
1114 gint result;
1115 gboolean response;
1116
1117 /* Initialize dialog */
1118 pdialog = g_new0 (PasswordDialog, 1);
1119 passdlg_init (pdialog, parent);
1120
1121 dialog = pdialog->ui;
1122 wpassdlg = WID ("change-password");
1123
1124 /* Go! */
1125 gtk_widget_show_all (wpassdlg);
1126
1127 do {
1128 result = gtk_dialog_run (GTK_DIALOG (wpassdlg));
1129 response = passdlg_process_response (pdialog, result);
1130 } while (response);
1131
1132 /* Clean up */
1133 stop_passwd (pdialog);
1134 gtk_widget_destroy (wpassdlg);
1135 g_queue_free (pdialog->backend_stdin_queue);
1136 g_object_unref (dialog);
1137 g_free (pdialog);
1138 }
1139