[accounts-dialog] Use /usr/bin/passwd directly when changing your own password
- From: Matthias Clasen <matthiasc src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [accounts-dialog] Use /usr/bin/passwd directly when changing your own password
- Date: Thu, 4 Feb 2010 07:20:12 +0000 (UTC)
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]