[gnome-system-tools] Use 'passwd' to change password for self user



commit c682277023c3c258ae9bb5c556f5f1a97da83f0b
Author: Milan Bouchet-Valat <nalimilan club fr>
Date:   Mon Jan 25 12:01:50 2010 +0100

    Use 'passwd' to change password for self user
    
    Use the 'passwd' command to change password for current user, since this is the only allowed way to provide the current password to PAM. This allows keyrings and encrypted folders to keep working. This implementation is taken from gnome-about-me (gnome-control-center), modified to suit our needs in run-passwd.c. Require authentication before showing the dialog (when user is not self) or when trying to change the password-less login option. For self user, if only password is changed, no authentication is required.
    
    Move all code for password dialog to a new user-passwd.c file. Use a custom prepare_edit_dialog() method instead of run_edit_dialog(), since gtk_dialog_run() doesn't play well with the asynchronous way we run 'passwd'. Remove get_no_passwd_login_group() since oobs_groups_config_get_from_name() replaces it.

 interfaces/users.ui       |   86 ++++--
 po/POTFILES.in            |    2 +
 src/users/Makefile.am     |    4 +-
 src/users/callbacks.c     |   49 ---
 src/users/main.c          |    6 -
 src/users/run-passwd.c    |  738 +++++++++++++++++++++++++++++++++++++++++++++
 src/users/run-passwd.h    |   58 ++++
 src/users/user-password.c |  543 +++++++++++++++++++++++++++++++++
 src/users/user-settings.c |  187 ------------
 src/users/user-settings.h |    1 -
 10 files changed, 1399 insertions(+), 275 deletions(-)
---
diff --git a/interfaces/users.ui b/interfaces/users.ui
index 3a71368..b773a5c 100644
--- a/interfaces/users.ui
+++ b/interfaces/users.ui
@@ -1759,6 +1759,7 @@
     <property name="skip_taskbar_hint">True</property>
     <property name="skip_pager_hint">True</property>
     <property name="has_separator">False</property>
+    <signal name="response" handler="on_user_passwd_dialog_response"/>
     <child internal-child="vbox">
       <object class="GtkVBox" id="dialog-vbox7">
         <property name="visible">True</property>
@@ -1782,8 +1783,8 @@
               <packing>
                 <property name="left_attach">1</property>
                 <property name="right_attach">2</property>
-                <property name="top_attach">3</property>
-                <property name="bottom_attach">4</property>
+                <property name="top_attach">4</property>
+                <property name="bottom_attach">5</property>
                 <property name="x_options">GTK_FILL</property>
                 <property name="y_options"></property>
               </packing>
@@ -1792,7 +1793,6 @@
               <object class="GtkEntry" id="user_settings_passwd1">
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
-                <property name="has_focus">True</property>
                 <property name="visibility">False</property>
                 <property name="invisible_char">&#x2022;</property>
                 <property name="activates_default">True</property>
@@ -1800,8 +1800,8 @@
               <packing>
                 <property name="left_attach">2</property>
                 <property name="right_attach">3</property>
-                <property name="top_attach">2</property>
-                <property name="bottom_attach">3</property>
+                <property name="top_attach">3</property>
+                <property name="bottom_attach">4</property>
                 <property name="y_options"></property>
               </packing>
             </child>
@@ -1816,8 +1816,8 @@
               <packing>
                 <property name="left_attach">2</property>
                 <property name="right_attach">3</property>
-                <property name="top_attach">3</property>
-                <property name="bottom_attach">4</property>
+                <property name="top_attach">4</property>
+                <property name="bottom_attach">5</property>
                 <property name="y_options"></property>
               </packing>
             </child>
@@ -1845,6 +1845,7 @@
                     <property name="visible">True</property>
                     <property name="sensitive">False</property>
                     <property name="can_focus">True</property>
+                    <property name="editable">False</property>
                     <property name="invisible_char">&#x2022;</property>
                     <property name="activates_default">True</property>
                   </object>
@@ -1858,6 +1859,7 @@
                     <property name="sensitive">False</property>
                     <property name="can_focus">True</property>
                     <property name="receives_default">True</property>
+                    <signal name="clicked" handler="on_user_settings_passwd_random_new"/>
                     <child>
                       <object class="GtkAlignment" id="alignment56">
                         <property name="visible">True</property>
@@ -1920,11 +1922,12 @@
                 <property name="use_underline">True</property>
                 <property name="active">True</property>
                 <property name="draw_indicator">True</property>
+                <signal name="toggled" handler="on_user_settings_passwd_toggled"/>
               </object>
               <packing>
                 <property name="right_attach">3</property>
-                <property name="top_attach">1</property>
-                <property name="bottom_attach">2</property>
+                <property name="top_attach">2</property>
+                <property name="bottom_attach">3</property>
                 <property name="x_options">GTK_FILL</property>
                 <property name="y_options"></property>
               </packing>
@@ -1934,32 +1937,15 @@
                 <property name="visible">True</property>
                 <property name="xalign">0</property>
                 <property name="yalign">0</property>
-                <property name="label" translatable="yes">User _password:</property>
+                <property name="label" translatable="yes">New _password:</property>
                 <property name="use_underline">True</property>
                 <property name="mnemonic_widget">user_settings_passwd1</property>
               </object>
               <packing>
                 <property name="left_attach">1</property>
                 <property name="right_attach">2</property>
-                <property name="top_attach">2</property>
-                <property name="bottom_attach">3</property>
-                <property name="x_options">GTK_FILL</property>
-                <property name="y_options"></property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkCheckButton" id="user_passwd_quality">
-                <property name="label" translatable="yes">Check password _quality</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
-                <property name="use_underline">True</property>
-                <property name="draw_indicator">True</property>
-              </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="right_attach">3</property>
-                <property name="top_attach">4</property>
-                <property name="bottom_attach">5</property>
+                <property name="top_attach">3</property>
+                <property name="bottom_attach">4</property>
                 <property name="x_options">GTK_FILL</property>
                 <property name="y_options"></property>
               </packing>
@@ -1973,6 +1959,7 @@
                 <property name="use_underline">True</property>
                 <property name="draw_indicator">True</property>
                 <property name="group">user_passwd_manual</property>
+                <signal name="toggled" handler="on_user_settings_passwd_toggled"/>
               </object>
               <packing>
                 <property name="right_attach">3</property>
@@ -2045,6 +2032,7 @@
                 <property name="receives_default">False</property>
                 <property name="use_underline">True</property>
                 <property name="draw_indicator">True</property>
+                <signal name="released" handler="on_user_passwd_no_check_released"/>
               </object>
               <packing>
                 <property name="right_attach">3</property>
@@ -2055,6 +2043,42 @@
               </packing>
             </child>
             <child>
+              <object class="GtkLabel" id="user_passwd_current_label">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="yalign">0</property>
+                <property name="label" translatable="yes">_Current password:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">user_passwd_current</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+                <property name="x_options">GTK_FILL</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="user_passwd_current">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="has_focus">True</property>
+                <property name="visibility">False</property>
+                <property name="invisible_char">&#x2022;</property>
+                <property name="activates_default">True</property>
+                <signal name="focus_out_event" handler="on_user_passwd_current_focus_out"/>
+              </object>
+              <packing>
+                <property name="left_attach">2</property>
+                <property name="right_attach">3</property>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
               <placeholder/>
             </child>
             <child>
@@ -3164,9 +3188,9 @@
   <object class="GtkSizeGroup" id="user_new_notice_sizegroup">
     <property name="mode">both</property>
     <widgets>
-      <widget name="user_new_login_notice"/>
-      <widget name="user_new_login_used_notice"/>
       <widget name="user_new_login_letter_notice"/>
+      <widget name="user_new_login_used_notice"/>
+      <widget name="user_new_login_notice"/>
     </widgets>
   </object>
 </interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 33d0195..702469c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -49,8 +49,10 @@ src/users/main.c
 src/users/passwd.c
 src/users/privileges-table.c
 [type: gettext/ini]src/users/user-profiles.conf.in
+src/users/run-passwd.c
 src/users/table.c
 src/users/users.desktop.in.in
+src/users/user-password.c
 src/users/user-settings.c
 src/users/users-table.c
 src/users/users-tool.c
diff --git a/src/users/Makefile.am b/src/users/Makefile.am
index 11073a2..80b6d03 100644
--- a/src/users/Makefile.am
+++ b/src/users/Makefile.am
@@ -28,7 +28,9 @@ users_admin_SOURCES = \
 	privileges-table.c 	privileges-table.h \
 	group-members-table.c 	group-members-table.h \
 	user-profiles.c		user-profiles.h \
-	test-battery.c		test-battery.h
+	test-battery.c		test-battery.h	\
+	run-passwd.c		run-passwd.h	\
+	user-password.c
 
 toolpixmaps =
 
diff --git a/src/users/callbacks.c b/src/users/callbacks.c
index 7caa767..cba26e4 100644
--- a/src/users/callbacks.c
+++ b/src/users/callbacks.c
@@ -26,7 +26,6 @@
 #include <config.h>
 #include "gst.h"
 
-#include "passwd.h"
 #include "callbacks.h"
 #include "table.h"
 #include "user-settings.h"
@@ -287,54 +286,6 @@ on_group_delete_clicked (GtkButton *button, gpointer user_data)
 	g_list_free (list);
 }
 
-/* User settings callbacks */
-
-void
-on_user_settings_passwd_changed (GtkEntry *entry, gpointer data)
-{
-	g_object_set_data (G_OBJECT (entry), "changed", GINT_TO_POINTER (TRUE));
-}
-
-void
-on_user_settings_passwd_random_new (GtkButton *button, gpointer data)
-{
-	GtkWidget *widget;
-	gchar *passwd;
-
-	widget = gst_dialog_get_widget (tool->main_dialog, "user_settings_random_passwd");
-
-	passwd = passwd_get_random ();
-	gtk_entry_set_text (GTK_ENTRY (widget), passwd);
-	g_free (passwd);
-}
-
-void
-on_user_settings_passwd_toggled (GtkToggleButton *toggle, gpointer data)
-{
-	GtkWidget *user_passwd_random_new = gst_dialog_get_widget (tool->main_dialog, "user_passwd_random_new");
-	GtkWidget *user_passwd_random_entry = gst_dialog_get_widget (tool->main_dialog, "user_settings_random_passwd");
-	GtkWidget *user_passwd_entry1 = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd1");
-	GtkWidget *user_passwd_entry2 = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd2");
-	GtkToggleButton *pwd_manual = GTK_TOGGLE_BUTTON (gst_dialog_get_widget (tool->main_dialog, "user_passwd_manual"));
-	
-
-	if (gtk_toggle_button_get_active (pwd_manual)) {
-		gtk_widget_set_sensitive (user_passwd_random_new, FALSE);
-		gtk_widget_set_sensitive (user_passwd_random_entry, FALSE);
-		gtk_widget_set_sensitive (user_passwd_entry1, TRUE);
-		gtk_widget_set_sensitive (user_passwd_entry2, TRUE);
-		gtk_entry_set_text (GTK_ENTRY (user_passwd_random_entry), "");
-	} else {
-		gtk_widget_set_sensitive (user_passwd_random_new, TRUE);
-		gtk_widget_set_sensitive (user_passwd_random_entry, TRUE);
-		gtk_widget_set_sensitive (user_passwd_entry1, FALSE);
-		gtk_widget_set_sensitive (user_passwd_entry2, FALSE);
-		gtk_entry_set_text (GTK_ENTRY (user_passwd_entry1), "");
-		gtk_entry_set_text (GTK_ENTRY (user_passwd_entry2), "");
-		on_user_settings_passwd_random_new (NULL, NULL);
-	}
-}
-
 void
 on_groups_dialog_show_help (GtkWidget *widget, gpointer data)
 {
diff --git a/src/users/main.c b/src/users/main.c
index fa20989..4639b3c 100644
--- a/src/users/main.c
+++ b/src/users/main.c
@@ -40,12 +40,6 @@ GstTool *tool;
 void quit_cb (GstTool *tool, gpointer data);
 
 static GstDialogSignal signals[] = {
-	/* User settings dialog callbacks */
-	{ "user_passwd_manual",			"toggled",		G_CALLBACK (on_user_settings_passwd_toggled) },
-	{ "user_passwd_random",			"toggled",		G_CALLBACK (on_user_settings_passwd_toggled) },
-	{ "user_passwd_random_new",		"clicked",		G_CALLBACK (on_user_settings_passwd_random_new) },
-	{ "user_settings_passwd1",		"changed",		G_CALLBACK (on_user_settings_passwd_changed) },
-
 	/* Main dialog callbacks, users tab */
 	{ "user_delete",                	"clicked",       	G_CALLBACK (on_user_delete_clicked) },
 	{ "manage_groups",                      "clicked",              G_CALLBACK (on_manage_groups_clicked) },
diff --git a/src/users/run-passwd.c b/src/users/run-passwd.c
new file mode 100644
index 0000000..49efecd
--- /dev/null
+++ b/src/users/run-passwd.c
@@ -0,0 +1,738 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* run-passwd.c: this file is part of users-admin, a gnome-system-tools frontend
+ * for user administration.
+ *
+ * Copyright (C) 2002 Diego Gonzalez
+ * Copyright (C) 2006 Johannes H. Jensen
+ * Copyright (C) 2010 Milan Bouchet-Valat
+ *
+ * Written by: Diego Gonzalez <diego pemas net>
+ * Modified by: Johannes H. Jensen <joh deworks net>,
+ *              Milan Bouchet-Valat <nalimilan club fr>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Most of this code originally comes from gnome-about-me-password.c,
+ * from gnome-control-center.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <oobs/oobs.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#if __sun
+#include <sys/types.h>
+#include <signal.h>
+#endif
+
+#include "gst.h"
+#include "run-passwd.h"
+
+/* Passwd states */
+typedef enum {
+	PASSWD_STATE_NONE,		/* Passwd is not asking for anything */
+	PASSWD_STATE_AUTH,		/* Passwd is asking for our current password */
+	PASSWD_STATE_NEW,		/* Passwd is asking for our new password */
+	PASSWD_STATE_RETYPE,		/* Passwd is asking for our retyped new password */
+	PASSWD_STATE_ERR		/* Passwd reported an error but has not yet exited */
+} PasswdState;
+
+struct PasswdHandler {
+	GtkBuilder  *ui;
+
+	const char *current_password;
+	const char *new_password;
+	const char *retyped_password;
+
+	/* Communication with the passwd program */
+	GPid backend_pid;
+
+	GIOChannel *backend_stdin;
+	GIOChannel *backend_stdout;
+
+	GQueue *backend_stdin_queue;		/* Write queue to backend_stdin */
+
+	/* GMainLoop IDs */
+	guint backend_child_watch_id;		/* g_child_watch_add (PID) */
+	guint backend_stdout_watch_id;		/* g_io_add_watch (stdout) */
+
+	/* State of the passwd program */
+	PasswdState backend_state;
+	gboolean    changing_password;
+
+	PasswdCallback auth_cb;
+	gpointer       auth_cb_data;
+
+	PasswdCallback chpasswd_cb;
+	gpointer       chpasswd_cb_data;
+};
+
+/* Buffer size for backend output */
+#define BUFSIZE 64
+
+
+static GQuark
+passwd_error_quark (void)
+{
+	static GQuark q = 0;
+
+	if (q == 0) {
+		q = g_quark_from_static_string("passwd_error");
+	}
+
+	return q;
+}
+
+/* Error handling */
+#define PASSWD_ERROR (passwd_error_quark ())
+
+
+extern GstTool *tool;
+
+static void
+stop_passwd (PasswdHandler *passwd_handler);
+
+static void
+free_passwd_resources (PasswdHandler *passwd_handler);
+
+static gboolean
+io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler);
+
+
+/*
+ * Spawning and closing of backend {{
+ */
+
+/* Child watcher */
+static void
+child_watch_cb (GPid pid, gint status, PasswdHandler *passwd_handler)
+{
+	if (WIFEXITED (status)) {
+		if (WEXITSTATUS (status) >= 255) {
+			g_warning ("Child exited unexpectedly");
+		}
+	}
+
+	free_passwd_resources (passwd_handler);
+}
+
+/* Spawn passwd backend
+ * Returns: TRUE on success, FALSE otherwise and sets error appropriately */
+static gboolean
+spawn_passwd (PasswdHandler *passwd_handler, GError **error)
+{
+	gchar	*argv[2];
+	gchar	*envp[1];
+	gint	my_stdin, my_stdout, my_stderr;
+
+	argv[0] = "/usr/bin/passwd";	/* Is it safe to rely on a hard-coded path? */
+	argv[1] = NULL;
+
+	envp[0] = NULL;			/* If we pass an empty array as the environment,
+					 * will the childs environment be empty, and the
+					 * locales set to the C default? From the manual:
+					 * "If envp is NULL, the child inherits its
+					 * parent'senvironment."
+					 * If I'm wrong here, we somehow have to set
+					 * the locales here.
+					 */
+
+	if (!g_spawn_async_with_pipes (NULL,				/* Working directory */
+	                               argv,				/* Argument vector */
+	                               envp,				/* Environment */
+	                               G_SPAWN_DO_NOT_REAP_CHILD,	/* Flags */
+	                               NULL,				/* Child setup */
+	                               NULL,				/* Data to child setup */
+	                               &passwd_handler->backend_pid,	/* PID */
+	                               &my_stdin,			/* Stdin */
+	                               &my_stdout,			/* Stdout */
+	                               &my_stderr,			/* Stderr */
+	                               error)) {			/* GError */
+
+		/* An error occured */
+		free_passwd_resources (passwd_handler);
+
+		return FALSE;
+	}
+
+	/* 2>&1 */
+	if (dup2 (my_stderr, my_stdout) == -1) {
+		/* Failed! */
+		g_set_error_literal (error,
+		                     PASSWD_ERROR,
+		                     PASSWD_ERROR_BACKEND,
+		                     strerror (errno));
+
+		/* Clean up */
+		stop_passwd (passwd_handler);
+
+		return FALSE;
+	}
+
+	/* Open IO Channels */
+	passwd_handler->backend_stdin = g_io_channel_unix_new (my_stdin);
+	passwd_handler->backend_stdout = g_io_channel_unix_new (my_stdout);
+
+	/* Set raw encoding */
+	/* Set nonblocking mode */
+	if (g_io_channel_set_encoding (passwd_handler->backend_stdin, NULL, error) != G_IO_STATUS_NORMAL ||
+		g_io_channel_set_encoding (passwd_handler->backend_stdout, NULL, error) != G_IO_STATUS_NORMAL ||
+		g_io_channel_set_flags (passwd_handler->backend_stdin, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ||
+		g_io_channel_set_flags (passwd_handler->backend_stdout, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ) {
+
+		/* Clean up */
+		stop_passwd (passwd_handler);
+		return FALSE;
+	}
+
+	/* Turn off buffering */
+	g_io_channel_set_buffered (passwd_handler->backend_stdin, FALSE);
+	g_io_channel_set_buffered (passwd_handler->backend_stdout, FALSE);
+
+	/* Add IO Channel watcher */
+	passwd_handler->backend_stdout_watch_id = g_io_add_watch (passwd_handler->backend_stdout,
+	                                                          G_IO_IN | G_IO_PRI,
+	                                                          (GIOFunc) io_watch_stdout, passwd_handler);
+
+	/* Add child watcher */
+	passwd_handler->backend_child_watch_id = g_child_watch_add (passwd_handler->backend_pid, (GChildWatchFunc) child_watch_cb, passwd_handler);
+
+	/* Success! */
+
+	return TRUE;
+}
+
+/* Stop passwd backend */
+static void
+stop_passwd (PasswdHandler *passwd_handler)
+{
+	/* This is the standard way of returning from the dialog with passwd.
+	 * If we return this way we can safely kill passwd as it has completed
+	 * its task.
+	 */
+
+	if (passwd_handler->backend_pid != -1) {
+		kill (passwd_handler->backend_pid, 9);
+	}
+
+	/* We must run free_passwd_resources here and not let our child
+	 * watcher do it, since it will access invalid memory after the
+	 * dialog has been closed and cleaned up.
+	 *
+	 * If we had more than a single thread we'd need to remove
+	 * the child watch before trying to kill the child.
+	 */
+	free_passwd_resources (passwd_handler);
+}
+
+/* Clean up passwd resources */
+static void
+free_passwd_resources (PasswdHandler *passwd_handler)
+{
+	GError	*error = NULL;
+
+	/* Remove the child watcher */
+	if (passwd_handler->backend_child_watch_id != 0) {
+
+		g_source_remove (passwd_handler->backend_child_watch_id);
+
+		passwd_handler->backend_child_watch_id = 0;
+	}
+
+
+	/* Close IO channels (internal file descriptors are automatically closed) */
+	if (passwd_handler->backend_stdin != NULL) {
+
+		if (g_io_channel_shutdown (passwd_handler->backend_stdin, TRUE, &error) != G_IO_STATUS_NORMAL) {
+			g_warning ("Could not shutdown backend_stdin IO channel: %s", error->message);
+			g_error_free (error);
+			error = NULL;
+		}
+
+		g_io_channel_unref (passwd_handler->backend_stdin);
+		passwd_handler->backend_stdin = NULL;
+	}
+
+	if (passwd_handler->backend_stdout != NULL) {
+
+		if (g_io_channel_shutdown (passwd_handler->backend_stdout, TRUE, &error) != G_IO_STATUS_NORMAL) {
+			g_warning ("Could not shutdown backend_stdout IO channel: %s", error->message);
+			g_error_free (error);
+			error = NULL;
+		}
+
+		g_io_channel_unref (passwd_handler->backend_stdout);
+
+		passwd_handler->backend_stdout = NULL;
+	}
+
+	/* Remove IO watcher */
+	if (passwd_handler->backend_stdout_watch_id != 0) {
+
+		g_source_remove (passwd_handler->backend_stdout_watch_id);
+
+		passwd_handler->backend_stdout_watch_id = 0;
+	}
+
+	/* Close PID */
+	if (passwd_handler->backend_pid != -1) {
+
+		g_spawn_close_pid (passwd_handler->backend_pid);
+
+		passwd_handler->backend_pid = -1;
+	}
+
+	/* Clear backend state */
+	passwd_handler->backend_state = PASSWD_STATE_NONE;
+}
+
+/*
+ * }} Spawning and closing of backend
+ */
+
+/*
+ * Backend communication code {{
+ */
+
+/* Write the first element of queue through channel */
+static void
+io_queue_pop (GQueue *queue, GIOChannel *channel)
+{
+	gchar	*buf;
+	gsize	bytes_written;
+	GError	*error = NULL;
+
+	buf = g_queue_pop_head (queue);
+
+	if (buf != NULL) {
+
+		if (g_io_channel_write_chars (channel, buf, -1, &bytes_written, &error) != G_IO_STATUS_NORMAL) {
+			g_warning ("Could not write queue element \"%s\" to channel: %s", buf, error->message);
+			g_error_free (error);
+		}
+
+		/* Ensure passwords are cleared from memory */
+		memset (buf, 0, strlen (buf));
+		g_free (buf);
+	}
+}
+
+/* Goes through the argument list, checking if one of them occurs in str
+ * Returns: TRUE as soon as an element is found to match, FALSE otherwise */
+static gboolean
+is_string_complete (gchar *str, ...)
+{
+	va_list	ap;
+	gchar	*arg;
+
+	if (strlen (str) == 0) {
+		return FALSE;
+	}
+
+	va_start (ap, str);
+
+	while ((arg = va_arg (ap, char *)) != NULL) {
+		if (g_strrstr (str, arg) != NULL) {
+			va_end (ap);
+			return TRUE;
+		}
+	}
+
+	va_end (ap);
+
+	return FALSE;
+}
+
+/*
+ * IO watcher for stdout, called whenever there is data to read from the backend.
+ * This is where most of the actual IO handling happens.
+ */
+static gboolean
+io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler)
+{
+	static GString *str = NULL;	/* Persistent buffer */
+
+	gchar		buf[BUFSIZE];		/* Temporary buffer */
+	gsize		bytes_read;
+	GError		*gio_error = NULL;	/* Error returned by functions */
+	GError		*error = NULL;		/* Error sent to callbacks */
+
+	GtkBuilder	*dialog;
+
+	gboolean	reinit = FALSE;
+
+	/* Initialize buffer */
+	if (str == NULL) {
+		str = g_string_new ("");
+	}
+
+	dialog = passwd_handler->ui;
+
+	if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &gio_error)
+	    != G_IO_STATUS_NORMAL) {
+		g_warning ("IO Channel read error: %s", gio_error->message);
+		g_error_free (gio_error);
+
+		return TRUE;
+	}
+
+	str = g_string_append_len (str, buf, bytes_read);
+
+	/* In which state is the backend? */
+	switch (passwd_handler->backend_state) {
+		case PASSWD_STATE_AUTH:
+			/* Passwd is asking for our current password */
+
+			if (is_string_complete (str->str, "assword: ", "failure", "wrong", "error", NULL)) {
+
+				if (g_strrstr (str->str, "assword: ") != NULL) {
+					/* Authentication successful */
+
+					passwd_handler->backend_state = PASSWD_STATE_NEW;
+
+					/* Trigger callback to update authentication status */
+					if (passwd_handler->auth_cb)
+						passwd_handler->auth_cb (passwd_handler,
+						                         NULL,
+						                         passwd_handler->auth_cb_data);
+
+				} else {
+					/* Authentication failed */
+
+					error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED,
+					                             _("Authentication failed"));
+
+					passwd_handler->changing_password = FALSE;
+
+					/* This error can happen both while authenticating or while changing password:
+					 * if chpasswd_cb is set, this means we're already changing password */
+					if (passwd_handler->chpasswd_cb)
+						passwd_handler->chpasswd_cb (passwd_handler,
+						                             error,
+						                             passwd_handler->auth_cb_data);
+					else if (passwd_handler->auth_cb)
+						passwd_handler->auth_cb (passwd_handler,
+						                         error,
+						                         passwd_handler->auth_cb_data);
+
+					g_error_free (error);
+				}
+
+				reinit = TRUE;
+			}
+			break;
+		case PASSWD_STATE_NEW:
+			/* Passwd is asking for our new password */
+
+			if (is_string_complete (str->str, "assword: ", NULL)) {
+				/* Advance to next state */
+				passwd_handler->backend_state = PASSWD_STATE_RETYPE;
+
+				/* Pop retyped password from queue and into IO channel */
+				io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+
+				reinit = TRUE;
+			}
+			break;
+		case PASSWD_STATE_RETYPE:
+			/* Passwd is asking for our retyped new password */
+
+			if (is_string_complete (str->str,
+			                        "successfully",
+			                        "short",
+			                        "longer",
+			                        "palindrome",
+			                        "simple",
+			                        "similar",
+			                        "wrapped",
+			                        "recovered",
+			                        "unchanged",
+			                        "match",
+			                        "1 numeric or special",
+			                        "failure",
+			                        NULL)) {
+
+				if (g_strrstr (str->str, "successfully") != NULL) {
+					/* Hooray! */
+
+					/* Trigger callback to update status */
+					if (passwd_handler->chpasswd_cb)
+						passwd_handler->chpasswd_cb (passwd_handler,
+						                             NULL,
+						                             passwd_handler->chpasswd_cb_data);
+				}
+				else {
+					/* Ohnoes! */
+
+					if (g_strrstr (str->str, "recovered") != NULL) {
+						/* What does this indicate?
+						 * "Authentication information cannot be recovered?" from libpam? */
+						error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN,
+						                             str->str);
+					} else if (g_strrstr (str->str, "short") != NULL ||
+						   g_strrstr (str->str, "longer") != NULL) {
+						error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+						                     _("The new password is too short"));
+					} else if (g_strrstr (str->str, "palindrome") != NULL ||
+						   g_strrstr (str->str, "simple") != NULL) {
+						error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+						                     _("The new password is too simple"));
+					} else if (g_strrstr (str->str, "similar") != NULL ||
+						   g_strrstr (str->str, "wrapped") != NULL) {
+						error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+						                     _("The old and new passwords are too similar"));
+					} else if (g_strrstr (str->str, "1 numeric or special") != NULL) {
+						error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+						                     _("The new password must contain numeric or special characters"));
+					} else if (g_strrstr (str->str, "unchanged") != NULL ||
+						   g_strrstr (str->str, "match") != NULL) {
+						error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+						                     _("The old and new passwords are the same"));
+					} else if (g_strrstr (str->str, "failure") != NULL) {
+						/* Authentication failure */
+						error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED,
+						                     _("Your password has been changed since you initially authenticated!"));
+					}
+					else {
+						error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN,
+						                     _("Unknown error"));
+					}
+
+					/* At this point, passwd might have exited, in which case
+					 * child_watch_cb should clean up for us and remove this watcher.
+					 * On some error conditions though, passwd just re-prompts us
+					 * for our new password. */
+					passwd_handler->backend_state = PASSWD_STATE_ERR;
+
+					passwd_handler->changing_password = FALSE;
+
+					/* Trigger callback to update status */
+					if (passwd_handler->chpasswd_cb)
+						passwd_handler->chpasswd_cb (passwd_handler,
+						                             error,
+						                             passwd_handler->chpasswd_cb_data);
+
+					g_error_free (error);
+
+				}
+
+				reinit = TRUE;
+
+				/* child_watch_cb should clean up for us now */
+			}
+			break;
+		case PASSWD_STATE_NONE:
+			/* Passwd is not asking for anything yet */
+			if (is_string_complete (str->str, "assword: ", NULL)) {
+
+				/* If the user does not have a password set,
+				 * passwd will immediately ask for the new password,
+				 * so skip the AUTH phase */
+				if (is_string_complete (str->str, "new", "New", NULL)) {
+					gchar *pw;
+
+					passwd_handler->backend_state = PASSWD_STATE_NEW;
+
+					/* since passwd didn't ask for our old password
+					 * in this case, simply remove it from the queue */
+					pw = g_queue_pop_head (passwd_handler->backend_stdin_queue);
+					g_free (pw);
+				} else {
+
+					passwd_handler->backend_state = PASSWD_STATE_AUTH;
+
+					/* Pop the IO queue, i.e. send current password */
+					io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+				}
+
+				reinit = TRUE;
+			}
+			break;
+		default:
+			/* Passwd has returned an error */
+			reinit = TRUE;
+			break;
+	}
+
+	if (reinit) {
+		g_string_free (str, TRUE);
+		str = NULL;
+	}
+
+	/* Continue calling us */
+	return TRUE;
+}
+
+/*
+ * }} Backend communication code
+ */
+
+/* Adds the current password to the IO queue */
+static void
+authenticate (PasswdHandler *passwd_handler)
+{
+	gchar	*s;
+
+	s = g_strdup_printf ("%s\n", passwd_handler->current_password);
+
+	g_queue_push_tail (passwd_handler->backend_stdin_queue, s);
+}
+
+/* Adds the new password twice to the IO queue */
+static void
+update_password (PasswdHandler *passwd_handler)
+{
+	gchar	*s;
+
+	s = g_strdup_printf ("%s\n", passwd_handler->new_password);
+
+	g_queue_push_tail (passwd_handler->backend_stdin_queue, s);
+	/* We need to allocate new space because io_queue_pop() g_free()s
+	 * every element of the queue after it's done */
+	g_queue_push_tail (passwd_handler->backend_stdin_queue, g_strdup (s));
+}
+
+
+PasswdHandler *
+passwd_init ()
+{
+	PasswdHandler *passwd_handler;
+
+	passwd_handler = g_new0 (PasswdHandler, 1);
+
+	/* Initialize backend_pid. -1 means the backend is not running */
+	passwd_handler->backend_pid = -1;
+
+	/* Initialize IO Channels */
+	passwd_handler->backend_stdin = NULL;
+	passwd_handler->backend_stdout = NULL;
+
+	/* Initialize write queue */
+	passwd_handler->backend_stdin_queue = g_queue_new ();
+
+	/* Initialize watchers */
+	passwd_handler->backend_child_watch_id = 0;
+	passwd_handler->backend_stdout_watch_id = 0;
+
+	/* Initialize backend state */
+	passwd_handler->backend_state = PASSWD_STATE_NONE;
+	passwd_handler->changing_password = FALSE;
+
+	return passwd_handler;
+}
+
+void
+passwd_destroy (PasswdHandler *passwd_handler)
+{
+	g_queue_free (passwd_handler->backend_stdin_queue);
+	stop_passwd (passwd_handler);
+	g_free (passwd_handler);
+}
+
+void
+passwd_authenticate (PasswdHandler *passwd_handler,
+                     const char    *current_password,
+                     PasswdCallback cb,
+                     const gpointer user_data)
+{
+	GError *error = NULL;
+
+	/* Don't stop if we've already started chaging password */
+	if (passwd_handler->changing_password)
+		return;
+
+	/* Clear data from possible previous attempts to change password */
+	passwd_handler->new_password = NULL;
+	passwd_handler->chpasswd_cb = NULL;
+	passwd_handler->chpasswd_cb_data = NULL;
+	g_queue_foreach (passwd_handler->backend_stdin_queue, (GFunc) g_free, NULL);
+	g_queue_clear (passwd_handler->backend_stdin_queue);
+
+	passwd_handler->current_password = current_password;
+	passwd_handler->auth_cb = cb;
+	passwd_handler->auth_cb_data = user_data;
+
+	/* Spawn backend */
+	stop_passwd (passwd_handler);
+
+	if (!spawn_passwd (passwd_handler, &error)) {
+		g_warning ("%s", error->message);
+		g_error_free (error);
+
+		return;
+	}
+
+	authenticate (passwd_handler);
+
+	/* Our IO watcher should now handle the rest */
+}
+
+gboolean
+passwd_change_password (PasswdHandler *passwd_handler,
+                        const char    *new_password,
+                        PasswdCallback cb,
+                        const gpointer user_data)
+{
+	GError *error = NULL;
+
+	passwd_handler->changing_password = TRUE;
+
+	passwd_handler->new_password = new_password;
+	passwd_handler->chpasswd_cb = cb;
+	passwd_handler->chpasswd_cb_data = user_data;
+
+	/* Stop passwd if an error occured and it is still running */
+	if (passwd_handler->backend_state == PASSWD_STATE_ERR) {
+
+		/* Stop passwd, free resources */
+		stop_passwd (passwd_handler);
+	}
+
+	/* Check that the backend is still running, or that an error
+	 * has occured but it has not yet exited */
+	if (passwd_handler->backend_pid == -1) {
+		/* If it is not, re-run authentication */
+
+		/* Spawn backend */
+		stop_passwd (passwd_handler);
+
+		if (!spawn_passwd (passwd_handler, &error)) {
+			g_warning ("%s", error->message);
+			g_error_free (error);
+
+			return FALSE;
+		}
+
+		/* Add current and new passwords to queue */
+		authenticate (passwd_handler);
+		update_password (passwd_handler);
+	} else {
+		/* Only add new passwords to queue */
+		update_password (passwd_handler);
+	}
+
+	/* Pop new password through the backend */
+	io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+
+	/* Our IO watcher should now handle the rest */
+
+	return TRUE;
+}
diff --git a/src/users/run-passwd.h b/src/users/run-passwd.h
new file mode 100644
index 0000000..99245e3
--- /dev/null
+++ b/src/users/run-passwd.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* run-passwd.h: this file is part of users-admin, a gnome-system-tools frontend
+ * for user administration.
+ *
+ * Copyright (C) 2010 Milan Bouchet-Valat
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors: Milan Bouchet-Valat <nalimilan club fr>
+ */
+
+#ifndef _RUN_PASSWD_H
+#define _RUN_PASSWD_H
+
+struct PasswdHandler;
+
+typedef struct PasswdHandler PasswdHandler;
+
+typedef void (*PasswdCallback) (PasswdHandler *passwd_handler, GError *error, const gpointer user_data);
+
+/* Error codes */
+typedef enum {
+	PASSWD_ERROR_REJECTED,		/* New password is not secure enough */
+	PASSWD_ERROR_AUTH_FAILED,	/* Wrong old password, or PAM failure */
+	PASSWD_ERROR_REAUTH_FAILED,	/* Password has changed since first authentication */
+	PASSWD_ERROR_BACKEND,		/* Backend error */
+	PASSWD_ERROR_UNKNOWN		/* General error */
+} PasswdError;
+
+
+PasswdHandler *passwd_init                ();
+
+void           passwd_destroy             (PasswdHandler *passwd_handler);
+
+void           passwd_authenticate        (PasswdHandler *passwd_handler,
+                                           const char    *current_password,
+                                           PasswdCallback cb,
+                                           gpointer       user_data);
+
+gboolean       passwd_change_password     (PasswdHandler *passwd_handler,
+                                           const char    *new_password,
+                                           PasswdCallback cb,
+                                           const gpointer user_data);
+
+#endif /* _RUN_PASSWD_H */
+
diff --git a/src/users/user-password.c b/src/users/user-password.c
new file mode 100644
index 0000000..87804c8
--- /dev/null
+++ b/src/users/user-password.c
@@ -0,0 +1,543 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* user-password.c: this file is part of users-admin, a gnome-system-tools frontend
+ * for user administration.
+ *
+ * Copyright (C) 2010 Milan Bouchet-Valat
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors: Milan Bouchet-Valat <nalimilan club fr>.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <oobs/oobs.h>
+#include <string.h>
+
+#include "gst.h"
+#include "users-tool.h"
+#include "users-table.h"
+#include "user-settings.h"
+#include "run-passwd.h"
+#include "passwd.h"
+
+
+#define NO_PASSWD_LOGIN_GROUP "nopasswdlogin"
+
+extern GstTool *tool;
+
+
+/* Callbacks */
+void on_user_settings_passwd_random_new   (GtkButton *button,
+                                           gpointer   data);
+
+void on_user_passwd_no_check_released     (GtkButton *button,
+                                           gpointer   data);
+
+void on_user_settings_passwd_toggled      (GtkToggleButton *toggle,
+                                           gpointer         data);
+
+void on_passwd_entry_changed              (GtkWidget *entry,
+                                           gpointer   user_data);
+
+void on_edit_user_passwd                  (GtkButton *button,
+                                           gpointer   user_data);
+
+gboolean on_user_passwd_current_focus_out (GtkWidget *entry,
+                                           GdkEventFocus *event,
+                                           gpointer user_data);
+
+void on_user_passwd_dialog_response       (GtkDialog *user_passwd_dialog,
+                                           int        response,
+                                           gpointer   user_data);
+
+/*
+ * Checks that password is valid. Returns it if it's the case,
+ * or NULL if it's not (showing an error).
+ */
+static const char*
+check_password (OobsUser *user)
+{
+	GtkWidget *dialog;
+	GtkWidget *widget;
+	const gchar *password, *confirmation;
+	char *primary_text = NULL;
+	char *secondary_text;
+	int len;
+
+	widget = gst_dialog_get_widget (tool->main_dialog, "user_passwd_manual");
+
+	/* manual password? */
+	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
+		widget = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd1");
+		password = gtk_entry_get_text (GTK_ENTRY (widget));
+
+		widget = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd2");
+		confirmation = gtk_entry_get_text (GTK_ENTRY (widget));
+	} else {
+		widget = gst_dialog_get_widget (tool->main_dialog, "user_settings_random_passwd");
+		password = confirmation = gtk_entry_get_text (GTK_ENTRY (widget));
+	}
+
+	len = strlen (password);
+
+	/* empty password, accept but don't change it */
+	if (len == 0) {
+		return password;
+	}
+	else if (len < 6) {
+		primary_text = _("Password is too short");
+		secondary_text = _("User passwords must be longer than 6 characters and preferably "
+		                   "formed by numbers, letters and special characters.");
+	} else if (strcmp (password, confirmation) != 0) {
+		primary_text = _("Password confirmation is not correct");
+		secondary_text = _("Check that you have provided the same password in both text fields.");
+	}
+
+	if (primary_text) {
+		dialog = gtk_message_dialog_new (GTK_WINDOW (tool->main_dialog),
+		                                 GTK_DIALOG_MODAL,
+		                                 GTK_MESSAGE_ERROR,
+		                                 GTK_BUTTONS_CLOSE,
+		                                 "%s", primary_text);
+
+		gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
+		                                            "%s", secondary_text);
+
+		gtk_dialog_run (GTK_DIALOG (dialog));
+		gtk_widget_destroy (dialog);
+		return NULL;
+	}
+
+	return password;
+}
+
+void
+on_user_settings_passwd_random_new (GtkButton *button,
+                                    gpointer   data)
+{
+	GtkWidget *widget;
+	gchar *passwd;
+
+	widget = gst_dialog_get_widget (tool->main_dialog, "user_settings_random_passwd");
+
+	passwd = passwd_get_random ();
+	gtk_entry_set_text (GTK_ENTRY (widget), passwd);
+	g_free (passwd);
+}
+
+void
+on_user_passwd_no_check_released (GtkButton *button,
+                                  gpointer   data)
+{
+	gboolean active;
+
+	/* Prevent modifications if not authenticated */
+	if (!gst_tool_authenticate (tool, GST_USERS_TOOL (tool)->users_config)) {
+		active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), !active);
+	}
+}
+
+void
+on_user_settings_passwd_toggled (GtkToggleButton *toggle,
+                                 gpointer         data)
+{
+	GtkWidget *user_passwd_random_new = gst_dialog_get_widget (tool->main_dialog, "user_passwd_random_new");
+	GtkWidget *user_passwd_random_entry = gst_dialog_get_widget (tool->main_dialog, "user_settings_random_passwd");
+	GtkWidget *user_passwd_entry1 = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd1");
+	GtkWidget *user_passwd_entry2 = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd2");
+	GtkWidget *validate_button = gst_dialog_get_widget (tool->main_dialog, "user_passwd_validate_button");
+	GtkToggleButton *pwd_manual = GTK_TOGGLE_BUTTON (gst_dialog_get_widget (tool->main_dialog, "user_passwd_manual"));
+
+	if (gtk_toggle_button_get_active (pwd_manual)) {
+		gtk_widget_set_sensitive (user_passwd_random_new, FALSE);
+		gtk_widget_set_sensitive (user_passwd_random_entry, FALSE);
+		gtk_widget_set_sensitive (user_passwd_entry1, TRUE);
+		gtk_widget_set_sensitive (user_passwd_entry2, TRUE);
+		gtk_entry_set_text (GTK_ENTRY (user_passwd_random_entry), "");
+	}
+	else {
+		gtk_widget_set_sensitive (user_passwd_random_new, TRUE);
+		gtk_widget_set_sensitive (user_passwd_random_entry, TRUE);
+		gtk_widget_set_sensitive (user_passwd_entry1, FALSE);
+		gtk_widget_set_sensitive (user_passwd_entry2, FALSE);
+		gtk_entry_set_text (GTK_ENTRY (user_passwd_entry1), "");
+		gtk_entry_set_text (GTK_ENTRY (user_passwd_entry2), "");
+		on_user_settings_passwd_random_new (NULL, NULL);
+
+		/* We know the random entry can't be empty */
+		gtk_widget_set_sensitive (validate_button, TRUE);
+	}
+}
+
+/*
+ * Alternative to run_edit_dialog() used by on_edit_user_passwd,
+ * which needs more flexibility than gtk_dialog_run() provides.
+ */
+static void
+prepare_edit_dialog (GtkDialog *dialog,
+                     GtkImage  *face_image2,
+                     GtkLabel  *name_label)
+{
+	OobsUser *user;
+	GtkWidget *face_image1;
+	GdkPixbuf *face;
+	const char *name;
+
+	/* Set user face from the main dialog image */
+	face_image1 = gst_dialog_get_widget (tool->main_dialog, "user_settings_face");
+	face = gtk_image_get_pixbuf (GTK_IMAGE (face_image1));
+	gtk_image_set_from_pixbuf (face_image2, face);
+
+	/* Set user name */
+	user = users_table_get_current ();
+	name = oobs_user_get_full_name (user);
+	gtk_label_set_text (name_label, name);
+	g_object_unref (user);
+
+	/* Run dialog with correct settings */
+	gtk_window_set_transient_for (GTK_WINDOW (dialog),
+	                              GTK_WINDOW (tool->main_dialog));
+	gst_dialog_add_edit_dialog (tool->main_dialog, GTK_WIDGET (dialog));
+	gtk_widget_show (GTK_WIDGET (dialog));
+}
+
+/*
+ * Callback for edit_user_name_button: run the dialog to change the user's
+ * password and apply changes if needed.
+ */
+void
+on_edit_user_passwd (GtkButton *button,
+                     gpointer   user_data)
+{
+	static gboolean once = TRUE;
+	GtkWidget *user_passwd_dialog;
+	GtkWidget *face_image;
+	GtkWidget *name_label;
+	GtkWidget *manual_toggle;
+	GtkWidget *passwd_entry;
+	GtkWidget *nocheck_toggle;
+	GtkWidget *user_passwd_current;
+	GtkWidget *current_label;
+	OobsUser *user;
+	OobsGroup *no_passwd_login_group;
+	PasswdHandler *passwd_handler = NULL;
+	gboolean is_self;
+
+	user = users_table_get_current ();
+	is_self = oobs_self_config_is_user_self (OOBS_SELF_CONFIG (GST_USERS_TOOL (tool)->self_config),
+	                                         user);
+
+	if (!is_self && !gst_tool_authenticate (tool, GST_USERS_TOOL (tool)->users_config))
+		return;
+
+	user_passwd_dialog = gst_dialog_get_widget (tool->main_dialog, "user_passwd_dialog");
+	face_image = gst_dialog_get_widget (tool->main_dialog, "user_passwd_face");
+	name_label = gst_dialog_get_widget (tool->main_dialog, "user_passwd_name");
+	nocheck_toggle = gst_dialog_get_widget (tool->main_dialog, "user_passwd_no_check");
+	manual_toggle = gst_dialog_get_widget (tool->main_dialog, "user_passwd_manual");
+	user_passwd_current = gst_dialog_get_widget (tool->main_dialog, "user_passwd_current");
+	current_label = gst_dialog_get_widget (tool->main_dialog, "user_passwd_current_label");
+
+	if (once) {
+		g_signal_connect (G_OBJECT (user_passwd_dialog),
+		                  "delete-event",
+		                  G_CALLBACK (gtk_widget_hide_on_delete),
+		                  NULL);
+	}
+
+	if (is_self)
+		passwd_handler = passwd_init ();
+
+	g_object_set_data (G_OBJECT (user_passwd_current), "passwd_handler", passwd_handler);
+
+	/* set manual password */
+	manual_toggle = gst_dialog_get_widget (tool->main_dialog, "user_passwd_manual");
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (manual_toggle), TRUE);
+
+	/* clear entries */
+	passwd_entry = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd1");
+	gtk_entry_set_text (GTK_ENTRY (passwd_entry), "");
+	gtk_widget_grab_focus (passwd_entry);
+
+	passwd_entry = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd2");
+	gtk_entry_set_text (GTK_ENTRY (passwd_entry), "");
+
+	gtk_entry_set_text (GTK_ENTRY (user_passwd_current), "");
+	gtk_widget_modify_base (user_passwd_current, GTK_STATE_NORMAL, NULL);
+
+	/* Reset current password widgets */
+	if (is_self) {
+		gtk_widget_show (user_passwd_current);
+		gtk_widget_show (current_label);
+
+		gtk_widget_grab_focus (user_passwd_current);
+	}
+	else {
+		gtk_widget_hide (user_passwd_current);
+		gtk_widget_hide (current_label);
+	}
+
+	/* set option to skip password check at login */
+	no_passwd_login_group =
+		oobs_groups_config_get_from_name (OOBS_GROUPS_CONFIG (GST_USERS_TOOL (tool)->groups_config),
+		                                  NO_PASSWD_LOGIN_GROUP);
+	/* root should not be allowed to login without password,
+	 * and we disable the feature if the group does not exist */
+	if (oobs_user_is_root (user) || no_passwd_login_group == NULL) {
+		gtk_widget_set_sensitive (nocheck_toggle, FALSE);
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (nocheck_toggle),
+		                              FALSE);
+	}
+	else {
+		gtk_widget_set_sensitive (nocheck_toggle, TRUE);
+		if (oobs_user_is_in_group (user, no_passwd_login_group))
+			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (nocheck_toggle),
+			                              TRUE);
+		else
+			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (nocheck_toggle),
+			                              FALSE);
+	}
+
+	prepare_edit_dialog (GTK_DIALOG (user_passwd_dialog),
+	                     GTK_IMAGE (face_image), GTK_LABEL (name_label));
+
+	g_object_unref (user);
+
+	if (no_passwd_login_group)
+		g_object_unref (no_passwd_login_group);
+}
+
+/*
+ * Called after the Validate button has been clicked, either directly,
+ * or asynchronously via chpasswd_cb(). Checks that changes to the password or
+ * to the password-less option should be committed, and close dialog.
+ */
+static void
+finish_password_change ()
+{
+	GtkWidget *user_passwd_dialog;
+	GtkWidget *nocheck_toggle;
+	OobsUser  *user;
+	OobsGroup *no_passwd_login_group;
+	gboolean   no_passwd_login_changed;
+	gboolean   is_self;
+
+	user_passwd_dialog = gst_dialog_get_widget (tool->main_dialog, "user_passwd_dialog");
+	nocheck_toggle = gst_dialog_get_widget (tool->main_dialog, "user_passwd_no_check");
+	user = users_table_get_current ();
+	is_self = oobs_self_config_is_user_self (OOBS_SELF_CONFIG (GST_USERS_TOOL (tool)->self_config),
+	                                         user);
+
+	/* check whether user is allowed to login without password */
+	no_passwd_login_group =
+		oobs_groups_config_get_from_name (OOBS_GROUPS_CONFIG (GST_USERS_TOOL (tool)->groups_config),
+		                                  NO_PASSWD_LOGIN_GROUP);
+	no_passwd_login_changed =
+		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (nocheck_toggle))
+		!= oobs_user_is_in_group (user, no_passwd_login_group);
+	if (no_passwd_login_changed) {
+
+		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (nocheck_toggle)))
+			oobs_group_add_user (no_passwd_login_group, user);
+		else
+			oobs_group_remove_user (no_passwd_login_group, user);
+	}
+
+	/* Only commit if password-less login option has changed, or
+	 * if user is not self (changing password via the backends). */
+	if (no_passwd_login_changed || !is_self) {
+		/* commit both user and groups config
+		 * because of the no_passwd_login_group membership */
+		if (gst_tool_commit (tool, OOBS_OBJECT (user)) == OOBS_RESULT_OK)
+			gst_tool_commit (tool, GST_USERS_TOOL (tool)->groups_config);
+	}
+
+	gst_dialog_remove_edit_dialog (tool->main_dialog, GTK_WIDGET (user_passwd_dialog));
+	gtk_widget_hide (GTK_WIDGET (user_passwd_dialog));
+
+	g_object_unref (user);
+
+	if (no_passwd_login_group)
+		g_object_unref (no_passwd_login_group);
+}
+
+static void
+auth_cb (PasswdHandler *passwd_handler,
+         GError        *error,
+         gpointer       user_data)
+{
+	GtkWidget *entry = GTK_WIDGET (user_data);
+	GdkColor color;
+
+	gdk_color_parse ("red", &color);
+	gtk_widget_modify_base (entry, GTK_STATE_NORMAL, error ? &color : NULL);
+}
+
+static void
+chpasswd_cb (PasswdHandler *passwd_handler,
+             GError        *error,
+             gpointer       user_data)
+{
+	GtkWidget *user_passwd_dialog;
+	GtkWidget *dialog;
+	GtkWidget *entry;
+	char *primary_text;
+	char *secondary_text;
+
+	user_passwd_dialog = gst_dialog_get_widget (tool->main_dialog, "user_passwd_dialog");
+
+	/* Restore dialog sensitivity and reset cursor */
+	gtk_widget_set_sensitive (GTK_WIDGET (user_passwd_dialog), TRUE);
+	gdk_window_set_cursor (gtk_widget_get_window (user_passwd_dialog), NULL);
+
+
+	if (!error) {
+		finish_password_change ();
+
+		passwd_destroy (passwd_handler);
+		return;
+	}
+
+	if (error->code == PASSWD_ERROR_REJECTED) {
+		primary_text = error->message;
+		secondary_text = _("Please choose another password.");
+
+		entry = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd1");
+		gtk_entry_set_text (GTK_ENTRY (entry), "");
+		gtk_widget_grab_focus (entry);
+
+		entry = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd2");
+		gtk_entry_set_text (GTK_ENTRY (entry), "");
+	}
+	else if (error->code == PASSWD_ERROR_AUTH_FAILED) {
+		primary_text = error->message;
+		secondary_text = _("Please type again your current password.");
+
+		entry = gst_dialog_get_widget (tool->main_dialog, "user_passwd_current");
+		gtk_widget_grab_focus (entry);
+		gtk_entry_set_text (GTK_ENTRY (entry), "");
+	}
+	else {
+		primary_text = _("Password could not be changed");
+		secondary_text = error->message;
+	}
+
+	dialog = gtk_message_dialog_new (GTK_WINDOW (user_passwd_dialog),
+	                                 GTK_DIALOG_MODAL,
+	                                 GTK_MESSAGE_ERROR,
+	                                 GTK_BUTTONS_CLOSE,
+	                                 "%s", primary_text);
+	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+	                                          "%s", secondary_text);
+
+	gtk_dialog_run (GTK_DIALOG (dialog));
+
+	gtk_widget_destroy (dialog);
+}
+
+gboolean
+on_user_passwd_current_focus_out (GtkWidget     *entry,
+                                  GdkEventFocus *event,
+                                  gpointer       user_data)
+{
+	PasswdHandler *passwd_handler;
+	const char *password;
+
+	password = gtk_entry_get_text (GTK_ENTRY (entry));
+
+	if (strlen (password) > 0) {
+		passwd_handler = g_object_get_data (G_OBJECT (entry), "passwd_handler");
+		passwd_authenticate (passwd_handler, password, auth_cb, entry);
+	}
+
+	return FALSE;
+}
+
+/*
+ * Callback for user_passwd_dialog: (try to) change password after performing checks.
+ */
+void
+on_user_passwd_dialog_response (GtkDialog *user_passwd_dialog,
+                                int        response,
+                                gpointer   user_data)
+{
+	GtkWidget *manual_toggle;
+	GtkWidget *nocheck_toggle;
+	GtkWidget *user_passwd_current;
+	OobsUser *user;
+	const char *passwd;
+	PasswdHandler *passwd_handler = NULL;
+	gboolean is_self;
+
+	nocheck_toggle = gst_dialog_get_widget (tool->main_dialog, "user_passwd_no_check");
+	manual_toggle = gst_dialog_get_widget (tool->main_dialog, "user_passwd_manual");
+	user_passwd_current = gst_dialog_get_widget (tool->main_dialog, "user_passwd_current");
+
+	user = users_table_get_current ();
+	is_self = oobs_self_config_is_user_self (OOBS_SELF_CONFIG (GST_USERS_TOOL (tool)->self_config),
+	                                         user);
+
+	passwd_handler = g_object_get_data (G_OBJECT (user_passwd_current), "passwd_handler");
+
+	if (response != GTK_RESPONSE_OK) {
+		gtk_widget_hide (GTK_WIDGET (user_passwd_dialog));
+
+		if (passwd_handler)
+			passwd_destroy (passwd_handler);
+
+		g_object_unref (user);
+		return;
+	}
+
+	/* Shows an error if password is not valid */
+	passwd = check_password (user);
+	if (!passwd) {
+		g_object_unref (user);
+		return;
+	}
+	/* If empty, directly handle password-less option, don't change password */
+	else if (strlen (passwd) == 0) {
+		finish_password_change ();
+		g_object_unref (user);
+		return;
+	}
+
+
+	/* For current user, change password via PAM */
+	if (is_self) {
+		passwd_change_password (passwd_handler, passwd, chpasswd_cb, NULL);
+		/* chpasswd_cb() will run finish_passwd_change() when done */
+
+		gtk_widget_set_sensitive (GTK_WIDGET (user_passwd_dialog), FALSE);
+		GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (user_passwd_dialog));
+		GdkCursor *cursor;
+		cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
+
+		gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (user_passwd_dialog)),
+		                       cursor);
+		gdk_display_flush (display);
+
+		gdk_cursor_unref (cursor);
+	}
+	/* For other users, set password via the backends */
+	else {
+		oobs_user_set_password (user, passwd);
+		finish_password_change ();
+	}
+
+	g_object_unref (user);
+}
diff --git a/src/users/user-settings.c b/src/users/user-settings.c
index 723ef7a..2d78ea9 100644
--- a/src/users/user-settings.c
+++ b/src/users/user-settings.c
@@ -241,35 +241,6 @@ select_main_group (OobsUser *user)
 		gtk_combo_box_set_active (GTK_COMBO_BOX (combo), -1);
 }
 
-/* Retrieve the NO_PASSWD_LOGIN_GROUP.
- * Don't forget to unref the returned group when done. */
-static OobsGroup *
-get_no_passwd_login_group ()
-{
-	OobsGroupsConfig *config;
-	OobsList *groups_list;
-	OobsListIter iter;
-	OobsGroup *group;
-	gboolean valid;
-	const gchar *group_name;
-
-	config = OOBS_GROUPS_CONFIG (GST_USERS_TOOL (tool)->groups_config);
-	groups_list = oobs_groups_config_get_groups (config);
-	valid = oobs_list_get_iter_first (groups_list, &iter);
-
-	while (valid) {
-		group = OOBS_GROUP (oobs_list_get (groups_list, &iter));
-		group_name = oobs_group_get_name (group);
-
-		if (group_name && strcmp (NO_PASSWD_LOGIN_GROUP, group_name) == 0)
-			return group;
-
-		valid = oobs_list_iter_next (groups_list, &iter);
-	}
-
-	return NULL;
-}
-
 static void
 set_login_length (GtkWidget *entry)
 {
@@ -468,63 +439,6 @@ check_shell (gchar **primary_text, gchar **secondary_text, gpointer data)
 	}
 }
 
-static gboolean
-check_password (OobsUser *user)
-{
-	GtkWidget *dialog;
-	GtkWidget *widget;
-	const gchar *password, *confirmation;
-	char *primary_text = NULL;
-	char *secondary_text;
-	int len;
-
-	widget = gst_dialog_get_widget (tool->main_dialog, "user_passwd_manual");
-
-	/* manual password? */
-	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
-		widget = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd1");
-		password = gtk_entry_get_text (GTK_ENTRY (widget));
-
-		widget = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd2");
-		confirmation = gtk_entry_get_text (GTK_ENTRY (widget));
-	} else {
-		widget = gst_dialog_get_widget (tool->main_dialog, "user_settings_random_passwd");
-		password = confirmation = gtk_entry_get_text (GTK_ENTRY (widget));
-	}
-
-	len = strlen (password);
-
-	/* empty password, accept but don't change it */
-	if (len == 0) {
-		return TRUE;
-	}
-	else if (len < 6) {
-		primary_text = _("Password is too short");
-		secondary_text = _("User passwords must be longer than 6 characters and preferably "
-		                   "formed by numbers, letters and special characters.");
-	} else if (strcmp (password, confirmation) != 0) {
-		primary_text = _("Password confirmation is not correct");
-		secondary_text = _("Check that you have provided the same password in both text fields.");
-	}
-
-	if (primary_text) {
-		dialog = gtk_message_dialog_new (GTK_WINDOW (tool->main_dialog),
-		                                 GTK_DIALOG_MODAL,
-		                                 GTK_MESSAGE_ERROR,
-		                                 GTK_BUTTONS_CLOSE,
-		                                 "%s", primary_text);
-
-		gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
-		                                            "%s", secondary_text);
-
-		gtk_dialog_run (GTK_DIALOG (dialog));
-		gtk_widget_destroy (dialog);
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
 /*
  * Check that current user is not removing himself from the admin group: warn if it's the case.
  * Also, if selected user is the only admin left, we directly show an error dialog.
@@ -1091,107 +1005,6 @@ on_edit_user_name (GtkButton *button, gpointer user_data)
 }
 
 /*
- * Callback for edit_user_name_button: run the dialog to change the user's
- * password and apply changes if needed.
- */
-void
-on_edit_user_passwd (GtkButton *button, gpointer user_data)
-{
-	int response;
-	GtkWidget *user_passwd_dialog;
-	GtkWidget *face_image;
-	GtkWidget *name_label;
-	GtkWidget *manual_toggle;
-	GtkWidget *passwd_entry;
-	GtkWidget *nocheck_toggle;
-	OobsUser *user;
-	OobsGroup *no_passwd_login_group;
-	const char *passwd;
-
-	user_passwd_dialog = gst_dialog_get_widget (tool->main_dialog, "user_passwd_dialog");
-	face_image = gst_dialog_get_widget (tool->main_dialog, "user_passwd_face");
-	name_label = gst_dialog_get_widget (tool->main_dialog, "user_passwd_name");
-
-	user = users_table_get_current ();
-
-	/* set manual password */
-	manual_toggle = gst_dialog_get_widget (tool->main_dialog, "user_passwd_manual");
-	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (manual_toggle), TRUE);
-
-	/* clear entries */
-	passwd_entry = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd1");
-	set_entry_text (passwd_entry, NULL);
-
-	passwd_entry = gst_dialog_get_widget (tool->main_dialog, "user_settings_passwd2");
-	set_entry_text (passwd_entry, NULL);
-
-	/* set option to skip password check at login */
-	nocheck_toggle = gst_dialog_get_widget (tool->main_dialog, "user_passwd_no_check");
-	no_passwd_login_group = get_no_passwd_login_group ();
-	/* root should not be allowed to login without password,
-	 * and we disable the feature if the group does not exist */
-	if (oobs_user_is_root (user) || no_passwd_login_group == NULL) {
-		gtk_widget_set_sensitive (nocheck_toggle, FALSE);
-		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (nocheck_toggle),
-		                              FALSE);
-	}
-	else {
-		gst_dialog_try_set_sensitive (tool->main_dialog, nocheck_toggle, TRUE);
-		if (oobs_user_is_in_group (user, no_passwd_login_group))
-			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (nocheck_toggle),
-			                              TRUE);
-		else
-			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (nocheck_toggle),
-			                              FALSE);
-	}
-	if (no_passwd_login_group)
-		g_object_unref (no_passwd_login_group);
-
-
-	do {
-		response = run_edit_dialog (GTK_DIALOG (user_passwd_dialog),
-		                            GTK_IMAGE (face_image), GTK_LABEL (name_label));
-	} while (response == GTK_RESPONSE_OK && !check_password (user));
-
-	if (response != GTK_RESPONSE_OK){
-		g_object_unref (user);
-		return;
-	}
-
-	/* set manual or random password if needed */
-	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (manual_toggle)))
-		passwd_entry = gst_dialog_get_widget (tool->main_dialog,
-		                                      "user_settings_passwd1");
-	else
-		passwd_entry = gst_dialog_get_widget (tool->main_dialog,
-		                                      "user_settings_random_passwd");
-
-	passwd = gtk_entry_get_text (GTK_ENTRY (passwd_entry));
-
-	/* empty password means: don't change it */
-	if (strlen (passwd) > 0)
-		oobs_user_set_password (user, passwd);
-
-	/* check whether user is allowed to login without password */
-	no_passwd_login_group = get_no_passwd_login_group ();
-	if (!oobs_user_is_root (user) && no_passwd_login_group != NULL) {
-		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (nocheck_toggle)))
-			oobs_group_add_user (no_passwd_login_group, user);
-		else
-			oobs_group_remove_user (no_passwd_login_group, user);
-	}
-	if (no_passwd_login_group)
-		g_object_unref (no_passwd_login_group);
-
-	/* commit both user and groups config
-	 * because of the no_passwd_login_group membership */
-	if (gst_tool_commit (tool, OOBS_OBJECT (user)) == OOBS_RESULT_OK)
-		gst_tool_commit (tool, GST_USERS_TOOL (tool)->groups_config);
-
-	g_object_unref (user);
-}
-
-/*
  * Callback for edit_user_profile_button: run the dialog to change the user's
  * account type and apply changes if needed.
  */
diff --git a/src/users/user-settings.h b/src/users/user-settings.h
index cb53743..4d22b59 100644
--- a/src/users/user-settings.h
+++ b/src/users/user-settings.h
@@ -29,7 +29,6 @@
 #include "users-tool.h"
 #include "user-profiles.h"
 
-#define NO_PASSWD_LOGIN_GROUP "nopasswdlogin"
 #define ADMIN_GROUP "admin"
 
 gboolean        user_delete                      (GtkTreeModel *model,



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]