summaryrefslogtreecommitdiffstats
path: root/panels/user-accounts/run-passwd.c
diff options
context:
space:
mode:
Diffstat (limited to 'panels/user-accounts/run-passwd.c')
-rw-r--r--panels/user-accounts/run-passwd.c737
1 files changed, 737 insertions, 0 deletions
diff --git a/panels/user-accounts/run-passwd.c b/panels/user-accounts/run-passwd.c
new file mode 100644
index 0000000..edbc998
--- /dev/null
+++ b/panels/user-accounts/run-passwd.c
@@ -0,0 +1,737 @@
+/* -*- 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, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 <stdio.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_DONE, /* Passwd succeeded but has not yet exited */
+ PASSWD_STATE_ERR /* Passwd reported an error but has not yet exited */
+} PasswdState;
+
+struct PasswdHandler {
+ const char *current_password;
+ const char *new_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");
+ }
+ if (WEXITSTATUS (status) == 0) {
+ if (passwd_handler->backend_state == PASSWD_STATE_RETYPE) {
+ passwd_handler->backend_state = PASSWD_STATE_DONE;
+ if (passwd_handler->chpasswd_cb)
+ passwd_handler->chpasswd_cb (passwd_handler,
+ NULL,
+ passwd_handler->chpasswd_cb_data);
+ }
+ }
+ }
+
+ free_passwd_resources (passwd_handler);
+}
+
+static void
+child_setup_cb (gpointer data)
+{
+ signal (SIGPIPE, SIG_IGN);
+ dup2 (fileno (stdout), fileno (stderr));
+}
+
+/* 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;
+ gint my_stdin, my_stdout;
+
+ argv[0] = "/usr/bin/passwd"; /* Is it safe to rely on a hard-coded path? */
+ argv[1] = NULL;
+
+ envp = g_get_environ ();
+ envp = g_environ_setenv (envp, "LC_ALL", "C", TRUE);
+
+ if (!g_spawn_async_with_pipes (NULL, /* Working directory */
+ argv, /* Argument vector */
+ envp, /* Environment */
+ G_SPAWN_DO_NOT_REAP_CHILD, /* Flags */
+ child_setup_cb, /* Child setup */
+ NULL, /* Data to child setup */
+ &passwd_handler->backend_pid, /* PID */
+ &my_stdin, /* Stdin */
+ &my_stdout, /* Stdout */
+ NULL, /* Stderr */
+ error)) { /* GError */
+
+ /* An error occurred */
+ free_passwd_resources (passwd_handler);
+
+ g_strfreev (envp);
+
+ return FALSE;
+ }
+
+ g_strfreev (envp);
+
+ /* 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)
+{
+ /* 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) {
+ g_autoptr(GError) error = 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_clear_pointer (&passwd_handler->backend_stdin, g_io_channel_unref);
+ }
+
+ if (passwd_handler->backend_stdout != NULL) {
+ g_autoptr(GError) error = 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_clear_pointer (&passwd_handler->backend_stdout, g_io_channel_unref);
+ }
+
+ /* 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)
+{
+ g_autofree gchar *buf = NULL;
+ gsize bytes_written;
+ g_autoptr(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);
+ }
+
+ /* Ensure passwords are cleared from memory */
+ memset (buf, 0, strlen (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 (strstr (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;
+ g_autoptr(GError) gio_error = NULL;
+
+ 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);
+ 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 (strstr (str->str, "assword: ") != NULL &&
+ strstr (str->str, "incorrect") == NULL &&
+ strstr (str->str, "urrent") == 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 */
+ g_autoptr(GError) error = NULL;
+
+ 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->chpasswd_cb_data);
+ else if (passwd_handler->auth_cb)
+ passwd_handler->auth_cb (passwd_handler,
+ error,
+ passwd_handler->auth_cb_data);
+ }
+
+ 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",
+ "simplistic",
+ "similar",
+ "case",
+ "different",
+ "wrapped",
+ "recovered",
+ "recent",
+ "unchanged",
+ "match",
+ "1 numeric or special",
+ "failure",
+ "DIFFERENT",
+ "BAD PASSWORD",
+ NULL)) {
+
+ if (strstr (str->str, "successfully") != NULL) {
+ /* Hooray! */
+
+ passwd_handler->backend_state = PASSWD_STATE_DONE;
+ /* Trigger callback to update status */
+ if (passwd_handler->chpasswd_cb)
+ passwd_handler->chpasswd_cb (passwd_handler,
+ NULL,
+ passwd_handler->chpasswd_cb_data);
+ }
+ else {
+ /* Ohnoes! */
+ g_autoptr(GError) error = NULL;
+
+ if (strstr (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 (strstr (str->str, "short") != NULL ||
+ strstr (str->str, "longer") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password is too short"));
+ } else if (strstr (str->str, "palindrome") != NULL ||
+ strstr (str->str, "simple") != NULL ||
+ strstr (str->str, "simplistic") != NULL ||
+ strstr (str->str, "dictionary") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password is too simple"));
+ } else if (strstr (str->str, "similar") != NULL ||
+ strstr (str->str, "different") != NULL ||
+ strstr (str->str, "case") != NULL ||
+ strstr (str->str, "wrapped") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The old and new passwords are too similar"));
+ } else if (strstr (str->str, "recent") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password has already been used recently."));
+ } else if (strstr (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 (strstr (str->str, "unchanged") != NULL ||
+ strstr (str->str, "match") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The old and new passwords are the same"));
+ } else if (strstr (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 if (strstr (str->str, "DIFFERENT")) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password does not contain enough different characters"));
+ }
+ 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);
+ }
+
+ 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)) {
+ g_autofree gchar *pw = NULL;
+
+ 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);
+
+ /* Pop the IO queue, i.e. send new password */
+ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+
+ } 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 (void)
+{
+ 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)
+{
+ g_autoptr(GError) error = NULL;
+
+ /* Don't stop if we've already started changing 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);
+ 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)
+{
+ 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 occurred 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 occurred but it has not yet exited */
+ if (passwd_handler->backend_pid == -1) {
+ /* If it is not, re-run authentication */
+ g_autoptr(GError) error = NULL;
+
+ /* Spawn backend */
+ stop_passwd (passwd_handler);
+
+ if (!spawn_passwd (passwd_handler, &error)) {
+ g_warning ("%s", error->message);
+ 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.
+ * If user has no password, popping the queue would output current
+ * password, while 'passwd' is waiting for the new one. So wait for
+ * io_watch_stdout() to remove current password from the queue,
+ * and output the new one for us.
+ */
+ if (passwd_handler->current_password)
+ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+
+ /* Our IO watcher should now handle the rest */
+
+ return TRUE;
+}