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.
         * 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;
}
/* Called when the "Change password" dialog-button is clicked
 * Returns: TRUE if we want to keep the dialog running, FALSE otherwise */
static gboolean
passdlg_process_response (PasswordDialog *pdialog, gint response_id)
{

	if (response_id == GTK_RESPONSE_OK) {
		/* Set busy as this can be a long process */
		passdlg_set_busy (pdialog, TRUE);

		/* Stop passwd if an error occured and it is still running */
		if (pdialog->backend_state == PASSWD_STATE_ERR) {

			/* Stop passwd, free resources */
			stop_passwd (pdialog);
		}

		/* Check that the backend is still running, or that an error
		 * hass occured but it has not yet exited */
		if (pdialog->backend_pid == -1) {
			/* If it is not, re-run authentication */

			/* Spawn backend */
			if (!passdlg_spawn_passwd (pdialog)) {
				return TRUE;
			}

			/* Add current and new passwords to queue */
			authenticate (pdialog);
			update_password (pdialog);
		} else {
			/* Only add new passwords to queue */
			update_password (pdialog);

			/* Pop new password through the backend */
			io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin);
		}

		/* Our IO watcher should now handle the rest */

		/* Keep the dialog running */
		return TRUE;
	}

	return FALSE;
}
/* Authentication attempt succeeded. Update the GUI accordingly. */
static void
authenticated_user (PasswordDialog *pdialog)
{
	pdialog->backend_state = PASSWD_STATE_NEW;

	if (pdialog->authenticated) {
		/* This is a re-authentication
		 * It succeeded, so pop our new password from the queue */
		io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin);
	}

	/* Update UI state */
	passdlg_set_auth_state (pdialog, TRUE);
	passdlg_set_status (pdialog, _("Authenticated!"));

	/* Check to see if the passwords are valid
	 * (They might be non-empty if the user had to re-authenticate,
	 *  and thus we need to enable the change-password-button) */
	passdlg_refresh_password_state (pdialog);
}
/*
 * 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, PasswordDialog *pdialog)
{
	static GString *str = NULL;	/* Persistent buffer */

	gchar		buf[BUFSIZE];		/* Temporary buffer */
	gsize		bytes_read;
	GError		*error = NULL;

	gchar		*msg = NULL;		/* Status error message */
	GtkBuilder	*dialog;

	gboolean	reinit = FALSE;

	/* Initialize buffer */
	if (str == NULL) {
		str = g_string_new ("");
	}

	dialog = pdialog->ui;

	if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &error) != G_IO_STATUS_NORMAL) {
		g_warning ("IO Channel read error: %s", error->message);
		g_error_free (error);

		return TRUE;
	}

	str = g_string_append_len (str, buf, bytes_read);

	/* In which state is the backend? */
	switch (pdialog->backend_state) {
		case PASSWD_STATE_AUTH:
			/* Passwd is asking for our current password */

			if (is_string_complete (str->str, "assword: ", "failure", "wrong", "error", NULL)) {
				/* Which response did we get? */
				passdlg_set_busy (pdialog, FALSE);

				if (g_strrstr (str->str, "assword: ") != NULL) {
					/* Authentication successful */

					authenticated_user (pdialog);

				} else {
					/* Authentication failed */

					if (pdialog->authenticated) {
						/* This is a re-auth, and it failed.
						 * The password must have been changed in the meantime!
						 * Ask the user to re-authenticate
						 */

						/* Update status message and auth state */
						passdlg_set_status (pdialog, _("Your password has been changed since you initially authenticated! Please re-authenticate."));
					} else {
						passdlg_set_status (pdialog, _("That password was incorrect."));
					}

					/* Focus current password */
					gtk_widget_grab_focus (GTK_WIDGET (pdialog->current_password));
				}

				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 */
				pdialog->backend_state = PASSWD_STATE_RETYPE;

				/* Pop retyped password from queue and into IO channel */
				io_queue_pop (pdialog->backend_stdin_queue, pdialog->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",
							  "simpl", /* catches both simple and simplistic */
							  "similar",
							  "different",
							  "case",
							  "wrapped",
							  "recovered",
							  "recent"
							  "unchanged",
							  "match",
							  "1 numeric or special",
							  "failure",
							  NULL)) {

				/* What response did we get? */
				passdlg_set_busy (pdialog, FALSE);

				if (g_strrstr (str->str, "successfully") != NULL) {
					/* Hooray! */

					passdlg_clear (pdialog);
					passdlg_set_status (pdialog, _("Your password has been changed."));
				} else {
					/* Ohnoes! */

					/* Focus new password */
					gtk_widget_grab_focus (GTK_WIDGET (pdialog->new_password));

					if (g_strrstr (str->str, "recovered") != NULL) {
						/* What does this indicate?
						 * "Authentication information cannot be recovered?" from libpam? */
						msg = g_strdup_printf (_("System error: %s."), str->str);
					} else if (g_strrstr (str->str, "short") != NULL ||
						   g_strrstr (str->str, "longer") != NULL) {
						msg = g_strdup (_("The password is too short."));
					} else if (g_strrstr (str->str, "palindrome") != NULL ||
						   g_strrstr (str->str, "simpl") != NULL ||
						   g_strrstr (str->str, "dictionary") != NULL) {
						msg = g_strdup (_("The password is too simple."));
					} else if (g_strrstr (str->str, "similar") != NULL ||
					           g_strrstr (str->str, "different") != NULL ||
					           g_strrstr (str->str, "case") != NULL ||
						   g_strrstr (str->str, "wrapped") != NULL) {
						msg = g_strdup (_("The old and new passwords are too similar."));
					} else if (g_strrstr (str->str, "1 numeric or special") != NULL) {
						msg = g_strdup (_("The new password must contain numeric or special character(s)."));
					} else if (g_strrstr (str->str, "unchanged") != NULL ||
						   g_strrstr (str->str, "match") != NULL) {
						msg = g_strdup (_("The old and new passwords are the same."));
					} else if (g_strrstr (str->str, "recent") != NULL) {
						msg = g_strdup (_("The new password has already been used recently."));
					} else if (g_strrstr (str->str, "failure") != NULL) {
						/* Authentication failure */
						msg = g_strdup (_("Your password has been changed since you initially authenticated! Please re-authenticate."));

						passdlg_set_auth_state (pdialog, FALSE);
					}
				}

				reinit = TRUE;

				if (msg != NULL) {
					/* An error occured! */
					passdlg_set_status (pdialog, msg);
					g_free (msg);

					/* 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. */

					pdialog->backend_state = PASSWD_STATE_ERR;
				}

				/* 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;

					pdialog->backend_state = PASSWD_STATE_NEW;

					passdlg_set_busy (pdialog, FALSE);
					authenticated_user (pdialog);

					/* since passwd didn't ask for our old password
					 * in this case, simply remove it from the queue */
					pw = g_queue_pop_head (pdialog->backend_stdin_queue);
					g_free (pw);
				} else {

					pdialog->backend_state = PASSWD_STATE_AUTH;

					/* Pop the IO queue, i.e. send current password */
					io_queue_pop (pdialog->backend_stdin_queue, pdialog->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;
}
/*
 * 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 (strstr (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->chpasswd_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",
                                                "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! */

                                        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);

                                        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);

                                        /* 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;
}