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