[accounts-dialog] Use /usr/bin/passwd directly when changing your own password



commit d2996a71b5323bc1e08575d54fb2e6c4bbeebace
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Feb 4 02:17:35 2010 -0500

    Use /usr/bin/passwd directly when changing your own password
    
    This is somewhat ugly, but it preserves the audit trail and
    lets us update the keyring password.

 AUTHORS                  |    4 +
 data/password-dialog.ui  |   72 ++++-
 src/Makefile.am          |    2 +
 src/run-passwd.c         |  730 ++++++++++++++++++++++++++++++++++++++++++++++
 src/run-passwd.h         |   58 ++++
 src/um-account-dialog.c  |   62 +----
 src/um-password-dialog.c |  257 +++++++++++++++--
 src/um-utils.c           |   70 +++++
 src/um-utils.h           |    3 +
 9 files changed, 1163 insertions(+), 95 deletions(-)
---
diff --git a/AUTHORS b/AUTHORS
index d11be76..a217cb0 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -7,3 +7,7 @@ by Behdad Esfahbod.
 
 The shortname completion code was adapted from
 similar code in gnome-system-tools by Milan Bouchet-Valat.
+
+The code to run /usr/bin/passwd for changing passwords
+is based on similar code in gnome-control-center originally
+written by Diego Gonzalez.
diff --git a/data/password-dialog.ui b/data/password-dialog.ui
index 39b55cb..1930ac4 100644
--- a/data/password-dialog.ui
+++ b/data/password-dialog.ui
@@ -1,5 +1,7 @@
 <?xml version="1.0"?>
 <interface>
+  <!-- interface-requires gtk+ 2.12 -->
+  <!-- interface-naming-policy toplevel-contextual -->
   <object class="GtkListStore" id="action-model">
     <columns>
       <!-- column-name gchararray -->
@@ -33,10 +35,9 @@
   <object class="GtkDialog" id="dialog">
     <property name="border_width">5</property>
     <property name="title"> </property>
-    <property name="icon_name">system-config-users</property>
-    <property name="resizable">True</property>
     <property name="modal">True</property>
     <property name="window_position">center-on-parent</property>
+    <property name="icon_name">system-config-users</property>
     <property name="type_hint">dialog</property>
     <property name="has_separator">False</property>
     <child internal-child="vbox">
@@ -193,7 +194,7 @@
                   <object class="GtkTable" id="table4">
                     <property name="visible">True</property>
                     <property name="border_width">10</property>
-                    <property name="n_rows">4</property>
+                    <property name="n_rows">5</property>
                     <property name="n_columns">2</property>
                     <property name="column_spacing">10</property>
                     <property name="row_spacing">6</property>
@@ -421,6 +422,29 @@
                         <property name="right_attach">2</property>
                       </packing>
                     </child>
+                    <child>
+                      <object class="GtkLabel" id="old-password-label">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">Current password:</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">4</property>
+                        <property name="bottom_attach">5</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="old-password-entry">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="visibility">False</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">4</property>
+                        <property name="bottom_attach">5</property>
+                      </packing>
+                    </child>
                   </object>
                 </child>
                 <child type="tab">
@@ -440,7 +464,7 @@
                       <object class="GtkTable" id="table3">
                         <property name="visible">True</property>
                         <property name="border_width">10</property>
-                        <property name="n_rows">3</property>
+                        <property name="n_rows">4</property>
                         <property name="n_columns">2</property>
                         <property name="column_spacing">10</property>
                         <property name="row_spacing">6</property>
@@ -637,6 +661,29 @@
                             <property name="x_options">GTK_FILL</property>
                           </packing>
                         </child>
+                        <child>
+                          <object class="GtkLabel" id="old-password-label2">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">Current password:</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="old-password-entry2">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="visibility">False</property>
+                          </object>
+                          <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>
+                          </packing>
+                        </child>
                       </object>
                       <packing>
                         <property name="expand">False</property>
@@ -807,17 +854,20 @@
         </child>
       </object>
     </child>
+    <action-widgets>
+      <action-widget response="0">cancel-button</action-widget>
+      <action-widget response="0">ok-button</action-widget>
+    </action-widgets>
   </object>
   <object class="GtkSizeGroup" id="sizegroup">
-    <property name="ignore_hidden">False</property>
     <widgets>
-      <widget name="password-normal-password-label"/>
-      <widget name="password-normal-verify-label"/>
-      <widget name="password-normal-strength-label"/>
-      <widget name="password-normal-hint-label"/>
-      <widget name="password-memorable-length-label"/>
-      <widget name="password-memorable-password-label"/>
       <widget name="password-memorable-hint-label"/>
+      <widget name="password-memorable-password-label"/>
+      <widget name="password-memorable-length-label"/>
+      <widget name="password-normal-hint-label"/>
+      <widget name="password-normal-strength-label"/>
+      <widget name="password-normal-verify-label"/>
+      <widget name="password-normal-password-label"/>
     </widgets>
   </object>
 </interface>
diff --git a/src/Makefile.am b/src/Makefile.am
index 87bb9e8..b39be61 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -46,6 +46,8 @@ accounts_dialog_SOURCES = \
 	fingerprint-strings.h		\
 	um-strength-bar.h		\
 	um-strength-bar.c		\
+	run-passwd.h			\
+	run-passwd.c			\
 	$(MARSHALFILES)			\
 	main.c
 
diff --git a/src/run-passwd.c b/src/run-passwd.c
new file mode 100644
index 0000000..731ffc2
--- /dev/null
+++ b/src/run-passwd.c
@@ -0,0 +1,730 @@
+/* -*- 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 <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#if __sun
+#include <sys/types.h>
+#include <signal.h>
+#endif
+
+#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 {
+        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 ())
+
+
+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 */
+
+        gboolean        reinit = FALSE;
+
+        /* Initialize buffer */
+        if (str == NULL) {
+                str = g_string_new ("");
+        }
+
+        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",
+                                                "dictionary",
+                                                "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 ||
+                                                   g_strrstr (str->str, "dictionary") != 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/run-passwd.h b/src/run-passwd.h
new file mode 100644
index 0000000..1735ddb
--- /dev/null
+++ b/src/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/um-account-dialog.c b/src/um-account-dialog.c
index d9135e5..a8cdc51 100644
--- a/src/um-account-dialog.c
+++ b/src/um-account-dialog.c
@@ -128,46 +128,6 @@ is_shortname_used (const gchar *shortname)
         return pwent != NULL;
 }
 
-static gboolean
-query_tooltip (GtkWidget  *widget,
-               gint        x,
-               gint        y,
-               gboolean    keyboard_mode,
-               GtkTooltip *tooltip,
-               gpointer    user_data)
-{
-        gchar *tip;
-
-        if (GTK_ENTRY_ICON_SECONDARY == gtk_entry_get_icon_at_pos (GTK_ENTRY (widget), x, y)) {
-                tip = gtk_entry_get_icon_tooltip_text (GTK_ENTRY (widget),
-                                                       GTK_ENTRY_ICON_SECONDARY);
-                gtk_tooltip_set_text (tooltip, tip);
-                g_free (tip);
-
-                return TRUE;
-        }
-        else {
-                return FALSE;
-        }
-}
-
-static void
-icon_released (GtkEntry             *entry,
-              GtkEntryIconPosition  pos,
-              GdkEvent             *event,
-              gpointer              user_data)
-{
-        GtkSettings *settings;
-        gint timeout;
-
-        settings = gtk_widget_get_settings (GTK_WIDGET (entry));
-
-        g_object_get (settings, "gtk-tooltip-timeout", &timeout, NULL);
-        g_object_set (settings, "gtk-tooltip-timeout", 1, NULL);
-        gtk_tooltip_trigger_tooltip_query (gtk_widget_get_display (GTK_WIDGET (entry)));
-        g_object_set (settings, "gtk-tooltip-timeout", timeout, NULL);
-}
-
 static void
 shortname_changed (GtkComboBox     *combo,
                    UmAccountDialog *um)
@@ -205,17 +165,6 @@ shortname_changed (GtkComboBox     *combo,
         entry = gtk_bin_get_child (GTK_BIN (combo));
 
         if (!empty && (in_use || !valid)) {
-                gtk_entry_set_icon_from_stock (GTK_ENTRY (entry),
-                                               GTK_ENTRY_ICON_SECONDARY,
-                                               GTK_STOCK_DIALOG_ERROR);
-                gtk_entry_set_icon_activatable (GTK_ENTRY (entry),
-                                                GTK_ENTRY_ICON_SECONDARY,
-                                                TRUE);
-                g_signal_connect (entry, "icon-release",
-                                  G_CALLBACK (icon_released), FALSE);
-                g_signal_connect (entry, "query-tooltip",
-                                  G_CALLBACK (query_tooltip), NULL);
-
                 if (in_use) {
                         tip = g_strdup_printf (_("A user with the short name '%s' already exists."),
                                                shortname);
@@ -230,17 +179,12 @@ shortname_changed (GtkComboBox     *combo,
                                           " \xe2\x9e\xa3 any of the characters '.', '-' and '_'"));
                 }
 
-                g_object_set (entry, "has-tooltip", TRUE, NULL);
-                gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry),
-                                                 GTK_ENTRY_ICON_SECONDARY,
-                                                 tip);
+                set_entry_validation_error (GTK_ENTRY (entry), tip);
+
                 g_free (tip);
         }
         else {
-               g_object_set (entry, "has-tooltip", FALSE, NULL);
-               gtk_entry_set_icon_from_pixbuf (GTK_ENTRY (entry),
-                                               GTK_ENTRY_ICON_SECONDARY,
-                                               NULL);
+                clear_entry_validation_error (GTK_ENTRY (entry));
         }
 }
 
diff --git a/src/um-password-dialog.c b/src/um-password-dialog.c
index 6b346f5..a39df87 100644
--- a/src/um-password-dialog.c
+++ b/src/um-password-dialog.c
@@ -32,6 +32,10 @@
 #include "um-password-dialog.h"
 #include "um-user-manager.h"
 #include "um-strength-bar.h"
+#include "um-utils.h"
+#include "run-passwd.h"
+
+#define MIN_PASSWORD_LEN 6
 
 struct _UmPasswordDialog {
         GtkWidget *dialog;
@@ -52,7 +56,14 @@ struct _UmPasswordDialog {
         GtkWidget *ok_button;
 
         UmUser *user;
-        GRand *rand;
+
+        GtkWidget *old_password_label;
+        GtkWidget *old_password_entry;
+        GtkWidget *old_password_label2;
+        GtkWidget *old_password_entry2;
+        gboolean   old_password_ok;
+
+        PasswdHandler *passwd_handler;
 };
 
 static void
@@ -168,14 +179,84 @@ compute_password_strength (const gchar *password)
 }
 
 static void
-cancel_password_dialog (GtkButton        *button,
-                        UmPasswordDialog *um)
+finish_password_change (UmPasswordDialog *um)
 {
         gtk_widget_hide (um->dialog);
         um_password_dialog_set_user (um, NULL);
 }
 
 static void
+cancel_password_dialog (GtkButton        *button,
+                        UmPasswordDialog *um)
+{
+        finish_password_change (um);
+}
+
+static void
+dialog_closed (GtkWidget        *dialog,
+               gint              response_id,
+               UmPasswordDialog *um)
+{
+        gtk_widget_destroy (dialog);
+}
+
+static void
+password_changed_cb (PasswdHandler    *handler,
+                     GError           *error,
+                     UmPasswordDialog *um)
+{
+        GtkWidget *dialog;
+        const gchar *primary_text;
+        const gchar *secondary_text;
+        GtkWidget *entry;
+
+        gtk_widget_set_sensitive (um->dialog, TRUE);
+        gdk_window_set_cursor (gtk_widget_get_window (um->dialog), NULL);
+
+        if (!error) {
+                finish_password_change (um);
+                return;
+        }
+
+        if (error->code == PASSWD_ERROR_REJECTED) {
+                primary_text = error->message;
+                secondary_text = _("Please choose another password.");
+
+                gtk_entry_set_text (GTK_ENTRY (um->password_entry), "");
+                gtk_widget_grab_focus (um->password_entry);
+
+                gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
+        }
+        else if (error->code == PASSWD_ERROR_AUTH_FAILED) {
+                primary_text = error->message;
+                secondary_text = _("Please type again your current password.");
+
+                if (gtk_notebook_get_current_page (GTK_NOTEBOOK (um->notebook)) == 0)
+                        entry = um->old_password_entry;
+                else
+                        entry = um->old_password_entry2;
+                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 (um->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);
+        g_signal_connect (dialog, "response",
+                          G_CALLBACK (dialog_closed), um);
+        gtk_window_present (GTK_WINDOW (dialog));
+
+}
+
+static void
 accept_password_dialog (GtkButton        *button,
                         UmPasswordDialog *um)
 {
@@ -206,10 +287,26 @@ accept_password_dialog (GtkButton        *button,
                 hint = NULL;
         }
 
-        um_user_set_password (um->user, mode, password, hint);
-
-        gtk_widget_hide (um->dialog);
-        um_password_dialog_set_user (um, NULL);
+        if (mode == 0 && um_user_get_uid (um->user) == getuid ()) {
+                GdkDisplay *display;
+                GdkCursor *cursor;
+
+                /* When setting a password for the current user,
+                 * use passwd directly, to preserve the audit trail
+                 * and to e.g. update the keyring password.
+                 */
+                passwd_change_password (um->passwd_handler, password, password_changed_cb, um);
+                gtk_widget_set_sensitive (um->dialog, FALSE);
+                display = gtk_widget_get_display (um->dialog);
+                cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
+                gdk_window_set_cursor (gtk_widget_get_window (um->dialog), cursor);
+                gdk_display_flush (display);
+                gdk_cursor_unref (cursor);
+        }
+        else {
+                um_user_set_password (um->user, mode, password, hint);
+                finish_password_change (um);
+        }
 }
 
 static void
@@ -220,23 +317,29 @@ update_sensitivity (UmPasswordDialog *um)
         gboolean can_change;
 
         page = gtk_notebook_get_current_page (GTK_NOTEBOOK (um->notebook));
-        if (page != 0) {
+        switch (page) {
+        case 0:
+                password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
+                verify = gtk_entry_get_text (GTK_ENTRY (um->verify_entry));
+
+                /* TODO: configurable policies for acceptable passwords */
+                if (strcmp (password, verify) != 0)
+                        can_change = FALSE;
+                else if (strlen (password) < MIN_PASSWORD_LEN)
+                        can_change = FALSE;
+                else if (!um->old_password_ok)
+                        can_change = FALSE;
+                else
+                        can_change = TRUE;
+                break;
+        case 1:
+                can_change = um->old_password_ok;
+                break;
+        default:
                 can_change = TRUE;
-                goto out;
+                break;
         }
 
-        password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
-        verify = gtk_entry_get_text (GTK_ENTRY (um->verify_entry));
-
-        /* TODO: configurable policies for acceptable passwords */
-        if (strcmp (password, verify) != 0)
-                can_change = FALSE;
-        else if (strlen (password) < 6)
-                can_change = FALSE;
-        else
-                can_change = TRUE;
-
-out:
         gtk_widget_set_sensitive (um->ok_button, can_change);
 }
 
@@ -273,7 +376,7 @@ update_password_strength (UmPasswordDialog *um)
 
         strength = compute_password_strength (password);
 
-        if (strlen (password) < 6) {
+        if (strlen (password) < MIN_PASSWORD_LEN) {
                 strength = 0.0;
                 hint = C_("Password strength", "Too short");
         }
@@ -318,6 +421,70 @@ parent_size_changed (GtkWidget     *parent,
         gtk_widget_set_size_request (label, allocation->width - 2 * borderwidth - 10, -1);
 }
 
+static void
+auth_cb (PasswdHandler    *handler,
+         GError           *error,
+         UmPasswordDialog *um)
+{
+        GtkWidget *entry;
+
+        if (gtk_notebook_get_current_page (GTK_NOTEBOOK (um->notebook)) == 0)
+                entry = um->old_password_entry;
+        else
+                entry = um->old_password_entry2;
+
+        if (error) {
+                um->old_password_ok = FALSE;
+                set_entry_validation_error (GTK_ENTRY (entry),
+                                            _("Wrong password"));
+        }
+        else {
+                um->old_password_ok = TRUE;
+                clear_entry_validation_error (GTK_ENTRY (entry));
+        }
+
+        update_sensitivity (um);
+}
+
+static gboolean
+old_password_entry_focus_out (GtkWidget        *entry,
+                              GdkEventFocus    *event,
+                              UmPasswordDialog *um)
+{
+        const char *text;
+
+        text = gtk_entry_get_text (GTK_ENTRY (entry));
+        if (strlen (text) > 0) {
+                passwd_authenticate (um->passwd_handler, text,
+                                     (PasswdCallback)auth_cb, um);
+        }
+
+        return FALSE;
+}
+
+static void
+old_password_entry_activate (GtkWidget        *entry,
+                             UmPasswordDialog *um)
+{
+        const char *text;
+
+        text = gtk_entry_get_text (GTK_ENTRY (entry));
+        if (strlen (text) > 0) {
+                passwd_authenticate (um->passwd_handler, text,
+                                     (PasswdCallback)auth_cb, um);
+        }
+}
+
+
+static void
+old_password_entry_changed (GtkEntry         *entry,
+                            GParamSpec       *pspec,
+                            UmPasswordDialog *um)
+{
+        clear_entry_validation_error (GTK_ENTRY (entry));
+        um->old_password_ok = FALSE;
+        update_sensitivity (um);
+}
 
 UmPasswordDialog *
 um_password_dialog_new (void)
@@ -377,6 +544,26 @@ um_password_dialog_new (void)
                           G_CALLBACK (password_entry_changed), um);
         um->password_entry = widget;
 
+        widget = (GtkWidget *) gtk_builder_get_object (builder, "old-password-entry");
+        g_signal_connect (widget, "focus-out-event",
+                          G_CALLBACK (old_password_entry_focus_out), um);
+        g_signal_connect (widget, "notify::text",
+                          G_CALLBACK (old_password_entry_changed), um);
+        g_signal_connect (widget, "activate",
+                          G_CALLBACK (old_password_entry_activate), um);
+        um->old_password_entry = widget;
+        um->old_password_label = (GtkWidget *) gtk_builder_get_object (builder, "old-password-label");
+
+        widget = (GtkWidget *) gtk_builder_get_object (builder, "old-password-entry2");
+        g_signal_connect (widget, "focus-out-event",
+                          G_CALLBACK (old_password_entry_focus_out), um);
+        g_signal_connect (widget, "notify::text",
+                          G_CALLBACK (old_password_entry_changed), um);
+        g_signal_connect (widget, "activate",
+                          G_CALLBACK (old_password_entry_activate), um);
+        um->old_password_entry2 = widget;
+        um->old_password_label2 = (GtkWidget *) gtk_builder_get_object (builder, "old-password-label2");
+
         widget = (GtkWidget *) gtk_builder_get_object (builder, "verify-entry");
         g_signal_connect (widget, "notify::text",
                           G_CALLBACK (password_entry_changed), um);
@@ -440,8 +627,6 @@ um_password_dialog_new (void)
 
         g_object_unref (builder);
 
-        um->rand = g_rand_new ();
-
         generate_passwords (um);
 
         return um;
@@ -455,7 +640,8 @@ um_password_dialog_free (UmPasswordDialog *um)
         if (um->user)
                 g_object_unref (um->user);
 
-        g_rand_free (um->rand);
+        if (um->passwd_handler)
+                passwd_destroy (um->passwd_handler);
 
         g_free (um);
 }
@@ -466,6 +652,10 @@ um_password_dialog_set_user (UmPasswordDialog *um,
 {
         GdkPixbuf *pixbuf;
 
+        if (um->passwd_handler) {
+                passwd_destroy (um->passwd_handler);
+                um->passwd_handler = NULL;
+        }
         if (um->user) {
                 g_object_unref (um->user);
                 um->user = NULL;
@@ -486,6 +676,23 @@ um_password_dialog_set_user (UmPasswordDialog *um,
                 gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
                 gtk_entry_set_text (GTK_ENTRY (um->normal_hint_entry), "");
                 gtk_entry_set_text (GTK_ENTRY (um->generate_hint_entry), "");
+                gtk_entry_set_text (GTK_ENTRY (um->old_password_entry), "");
+                gtk_entry_set_text (GTK_ENTRY (um->old_password_entry2), "");
+                if (um_user_get_uid (um->user) == getuid()) {
+                        gtk_widget_show (um->old_password_label);
+                        gtk_widget_show (um->old_password_entry);
+                        gtk_widget_show (um->old_password_label2);
+                        gtk_widget_show (um->old_password_entry2);
+                        um->passwd_handler = passwd_init ();
+                        um->old_password_ok = FALSE;
+                }
+                else {
+                        gtk_widget_hide (um->old_password_label);
+                        gtk_widget_hide (um->old_password_entry);
+                        gtk_widget_hide (um->old_password_label2);
+                        gtk_widget_hide (um->old_password_entry2);
+                        um->old_password_ok = TRUE;
+                }
         }
 }
 
diff --git a/src/um-utils.c b/src/um-utils.c
index 5bb06b7..9360371 100644
--- a/src/um-utils.c
+++ b/src/um-utils.c
@@ -227,3 +227,73 @@ show_tooltip_now (GtkWidget *widget,
 
         return FALSE;
 }
+
+static gboolean
+query_tooltip (GtkWidget  *widget,
+               gint        x,
+               gint        y,
+               gboolean    keyboard_mode,
+               GtkTooltip *tooltip,
+               gpointer    user_data)
+{
+        gchar *tip;
+
+        if (GTK_ENTRY_ICON_SECONDARY == gtk_entry_get_icon_at_pos (GTK_ENTRY (widget), x, y)) {
+                tip = gtk_entry_get_icon_tooltip_text (GTK_ENTRY (widget),
+                                                       GTK_ENTRY_ICON_SECONDARY);
+                gtk_tooltip_set_text (tooltip, tip);
+                g_free (tip);
+
+                return TRUE;
+        }
+        else {
+                return FALSE;
+        }
+}
+
+static void
+icon_released (GtkEntry             *entry,
+              GtkEntryIconPosition  pos,
+              GdkEvent             *event,
+              gpointer              user_data)
+{
+        GtkSettings *settings;
+        gint timeout;
+
+        settings = gtk_widget_get_settings (GTK_WIDGET (entry));
+
+        g_object_get (settings, "gtk-tooltip-timeout", &timeout, NULL);
+        g_object_set (settings, "gtk-tooltip-timeout", 1, NULL);
+        gtk_tooltip_trigger_tooltip_query (gtk_widget_get_display (GTK_WIDGET (entry)));
+        g_object_set (settings, "gtk-tooltip-timeout", timeout, NULL);
+}
+
+
+void
+set_entry_validation_error (GtkEntry    *entry,
+                            const gchar *text)
+{
+        gtk_entry_set_icon_from_stock (entry,
+                                       GTK_ENTRY_ICON_SECONDARY,
+                                       GTK_STOCK_DIALOG_ERROR);
+        gtk_entry_set_icon_activatable (entry,
+                                        GTK_ENTRY_ICON_SECONDARY,
+                                        TRUE);
+        g_signal_connect (entry, "icon-release",
+                          G_CALLBACK (icon_released), FALSE);
+        g_signal_connect (entry, "query-tooltip",
+                          G_CALLBACK (query_tooltip), NULL);
+        g_object_set (entry, "has-tooltip", TRUE, NULL);
+        gtk_entry_set_icon_tooltip_text (entry,
+                                         GTK_ENTRY_ICON_SECONDARY,
+                                         text);
+}
+
+void
+clear_entry_validation_error (GtkEntry *entry)
+{
+        g_object_set (entry, "has-tooltip", FALSE, NULL);
+        gtk_entry_set_icon_from_pixbuf (entry,
+                                        GTK_ENTRY_ICON_SECONDARY,
+                                        NULL);
+}
diff --git a/src/um-utils.h b/src/um-utils.h
index dec37d3..b11cdfd 100644
--- a/src/um-utils.h
+++ b/src/um-utils.h
@@ -33,6 +33,9 @@ void     setup_tooltip_with_embedded_icon (GtkWidget   *widget,
 gboolean show_tooltip_now                 (GtkWidget   *widget,
                                            GdkEvent    *event);
 
+void     set_entry_validation_error       (GtkEntry    *entry,
+                                           const gchar *text);
+void     clear_entry_validation_error     (GtkEntry    *entry);
 G_END_DECLS
 
 #endif



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