diff options
author | Perberos <[email protected]> | 2011-12-01 21:51:44 -0300 |
---|---|---|
committer | Perberos <[email protected]> | 2011-12-01 21:51:44 -0300 |
commit | 0b0e6bc987da4fd88a7854ebb12bde705e92c428 (patch) | |
tree | 47d329edd31c67eaa36b2147780e37e197e901b5 /capplets/about-me/mate-about-me-password.c | |
download | mate-control-center-0b0e6bc987da4fd88a7854ebb12bde705e92c428.tar.bz2 mate-control-center-0b0e6bc987da4fd88a7854ebb12bde705e92c428.tar.xz |
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'capplets/about-me/mate-about-me-password.c')
-rw-r--r-- | capplets/about-me/mate-about-me-password.c | 1136 |
1 files changed, 1136 insertions, 0 deletions
diff --git a/capplets/about-me/mate-about-me-password.c b/capplets/about-me/mate-about-me-password.c new file mode 100644 index 00000000..0699c28d --- /dev/null +++ b/capplets/about-me/mate-about-me-password.c @@ -0,0 +1,1136 @@ +/* mate-about-me.c + * Copyright (C) 2002 Diego Gonzalez + * Copyright (C) 2006 Johannes H. Jensen + * + * Written by: Diego Gonzalez <[email protected]> + * Modified by: Johannes H. Jensen <[email protected]> + * + * 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. + * + * Parts of this code come from Mate-System-Tools. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +/* Are all of these needed? */ +#include <gdk/gdkkeysyms.h> +#include <pwd.h> +#include <stdlib.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 "capplet-util.h" +#include "eel-alert-dialog.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; + +typedef struct { + GtkBuilder *ui; + + /* Commonly used widgets */ + GtkEntry *current_password; + GtkEntry *new_password; + GtkEntry *retyped_password; + GtkImage *dialog_image; + GtkLabel *status_label; + + /* Whether we have authenticated */ + gboolean authenticated; + + /* 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; + +} PasswordDialog; + +/* Buffer size for backend output */ +#define BUFSIZE 64 + +/* + * Error handling {{ + */ +#define PASSDLG_ERROR (mate_about_me_password_error_quark()) + +GQuark mate_about_me_password_error_quark(void) +{ + static GQuark q = 0; + + if (q == 0) { + q = g_quark_from_static_string("mate_about_me_password_error"); + } + + return q; +} + +/* error codes */ +enum { + PASSDLG_ERROR_NONE, + PASSDLG_ERROR_NEW_PASSWORD_EMPTY, + PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY, + PASSDLG_ERROR_PASSWORDS_NOT_EQUAL, + PASSDLG_ERROR_BACKEND, /* Backend error */ + PASSDLG_ERROR_USER, /* Generic user error */ + PASSDLG_ERROR_FAILED /* Fatal failure, error->message should explain */ +}; + +/* + * }} Error handling + */ + +/* + * Prototypes {{ + */ +static void +stop_passwd (PasswordDialog *pdialog); + +static void +free_passwd_resources (PasswordDialog *pdialog); + +static gboolean +io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswordDialog *pdialog); + +static void +passdlg_set_auth_state (PasswordDialog *pdialog, gboolean state); + +static void +passdlg_set_status (PasswordDialog *pdialog, gchar *msg); + +static void +passdlg_set_busy (PasswordDialog *pdialog, gboolean busy); + +static void +passdlg_clear (PasswordDialog *pdialog); + +static guint +passdlg_refresh_password_state (PasswordDialog *pdialog); + +/* + * }} Prototypes + */ + +/* + * Spawning and closing of backend {{ + */ + +/* Child watcher */ +static void +child_watch_cb (GPid pid, gint status, PasswordDialog *pdialog) +{ + if (WIFEXITED (status)) { + if (WEXITSTATUS (status) >= 255) { + g_warning (_("Child exited unexpectedly")); + } + } + + free_passwd_resources (pdialog); +} + +/* Spawn passwd backend + * Returns: TRUE on success, FALSE otherwise and sets error appropriately */ +static gboolean +spawn_passwd (PasswordDialog *pdialog, 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 */ + &pdialog->backend_pid, /* PID */ + &my_stdin, /* Stdin */ + &my_stdout, /* Stdout */ + &my_stderr, /* Stderr */ + error)) { /* GError */ + + /* An error occured */ + free_passwd_resources (pdialog); + + return FALSE; + } + + /* 2>&1 */ + if (dup2 (my_stderr, my_stdout) == -1) { + /* Failed! */ + g_set_error (error, + PASSDLG_ERROR, + PASSDLG_ERROR_BACKEND, + strerror (errno)); + + /* Clean up */ + stop_passwd (pdialog); + + return FALSE; + } + + /* Open IO Channels */ + pdialog->backend_stdin = g_io_channel_unix_new (my_stdin); + pdialog->backend_stdout = g_io_channel_unix_new (my_stdout); + + /* Set raw encoding */ + /* Set nonblocking mode */ + if (g_io_channel_set_encoding (pdialog->backend_stdin, NULL, error) != G_IO_STATUS_NORMAL || + g_io_channel_set_encoding (pdialog->backend_stdout, NULL, error) != G_IO_STATUS_NORMAL || + g_io_channel_set_flags (pdialog->backend_stdin, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL || + g_io_channel_set_flags (pdialog->backend_stdout, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ) { + + /* Clean up */ + stop_passwd (pdialog); + return FALSE; + } + + /* Turn off buffering */ + g_io_channel_set_buffered (pdialog->backend_stdin, FALSE); + g_io_channel_set_buffered (pdialog->backend_stdout, FALSE); + + /* Add IO Channel watcher */ + pdialog->backend_stdout_watch_id = g_io_add_watch (pdialog->backend_stdout, + G_IO_IN | G_IO_PRI, + (GIOFunc) io_watch_stdout, pdialog); + + /* Add child watcher */ + pdialog->backend_child_watch_id = g_child_watch_add (pdialog->backend_pid, (GChildWatchFunc) child_watch_cb, pdialog); + + /* Success! */ + + return TRUE; +} + +/* Stop passwd backend */ +static void +stop_passwd (PasswordDialog *pdialog) +{ + /* 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 (pdialog->backend_pid != -1) { + kill (pdialog->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 (pdialog); +} + +/* Clean up passwd resources */ +static void +free_passwd_resources (PasswordDialog *pdialog) +{ + GError *error = NULL; + + /* Remove the child watcher */ + if (pdialog->backend_child_watch_id != 0) { + + g_source_remove (pdialog->backend_child_watch_id); + + pdialog->backend_child_watch_id = 0; + } + + + /* Close IO channels (internal file descriptors are automatically closed) */ + if (pdialog->backend_stdin != NULL) { + + if (g_io_channel_shutdown (pdialog->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 (pdialog->backend_stdin); + + pdialog->backend_stdin = NULL; + } + + if (pdialog->backend_stdout != NULL) { + + if (g_io_channel_shutdown (pdialog->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 (pdialog->backend_stdout); + + pdialog->backend_stdout = NULL; + } + + /* Remove IO watcher */ + if (pdialog->backend_stdout_watch_id != 0) { + + g_source_remove (pdialog->backend_stdout_watch_id); + + pdialog->backend_stdout_watch_id = 0; + } + + /* Close PID */ + if (pdialog->backend_pid != -1) { + + g_spawn_close_pid (pdialog->backend_pid); + + pdialog->backend_pid = -1; + } + + /* Clear backend state */ + pdialog->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); + } + + 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; +} + +/* 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; +} + +/* + * }} Backend communication code + */ + +/* Adds the current password to the IO queue */ +static void +authenticate (PasswordDialog *pdialog) +{ + gchar *s; + + s = g_strdup_printf ("%s\n", gtk_entry_get_text (pdialog->current_password)); + + g_queue_push_tail (pdialog->backend_stdin_queue, s); +} + +/* Adds the new password twice to the IO queue */ +static void +update_password (PasswordDialog *pdialog) +{ + gchar *s; + + s = g_strdup_printf ("%s\n", gtk_entry_get_text (pdialog->new_password)); + + g_queue_push_tail (pdialog->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 (pdialog->backend_stdin_queue, g_strdup (s)); +} + +/* Sets dialog busy state according to busy + * + * When busy: + * Sets the cursor to busy + * Disables the interface to prevent that the user interferes + * Reverts all this when non-busy + * + * Note that this function takes into account the + * authentication state of the dialog. So setting the + * dialog to busy and then back to normal should leave + * the dialog unchanged. + */ +static void +passdlg_set_busy (PasswordDialog *pdialog, gboolean busy) +{ + GtkBuilder *dialog; + GtkWidget *toplevel; + GdkCursor *cursor = NULL; + GdkDisplay *display; + + dialog = pdialog->ui; + + /* Set cursor */ + toplevel = WID ("change-password"); + display = gtk_widget_get_display (toplevel); + if (busy) { + cursor = gdk_cursor_new_for_display (display, GDK_WATCH); + } + + gdk_window_set_cursor (gtk_widget_get_window (toplevel), cursor); + gdk_display_flush (display); + + if (busy) { + gdk_cursor_unref (cursor); + } + + /* Disable/Enable UI */ + if (pdialog->authenticated) { + /* Authenticated state */ + + /* Enable/disable new password section */ + g_object_set (pdialog->new_password, "sensitive", !busy, NULL); + g_object_set (pdialog->retyped_password, "sensitive", !busy, NULL); + g_object_set (WID ("new-password-label"), "sensitive", !busy, NULL); + g_object_set (WID ("retyped-password-label"), "sensitive", !busy, NULL); + + /* Enable/disable change password button */ + g_object_set (WID ("change-password-button"), "sensitive", !busy, NULL); + + } else { + /* Not-authenticated state */ + + /* Enable/disable auth section state */ + g_object_set (pdialog->current_password, "sensitive", !busy, NULL); + g_object_set (WID ("authenticate-button"), "sensitive", !busy, NULL); + g_object_set (WID ("current-password-label"), "sensitive", !busy, NULL); + } +} + +/* Launch an error dialog */ +static void +passdlg_error_dialog (GtkWindow *parent, const gchar *title, + const gchar *msg, const gchar *details) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + msg); + if (title) + gtk_window_set_title (GTK_WINDOW (dialog), title); + + if (details) + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + details); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +/* Set authenticated state of dialog according to state + * + * When in authenticated state: + * Disables authentication-part of interface + * Enables new-password-part of interface + * When in not-authenticated state: + * Enables authentication-part of interface + * Disables new-password-part of interface + * Disables the change-password-button + */ +static void +passdlg_set_auth_state (PasswordDialog *pdialog, gboolean state) +{ + GtkBuilder *dialog; + + dialog = pdialog->ui; + + /* Widgets which require a not-authenticated state to be accessible */ + g_object_set (pdialog->current_password, "sensitive", !state, NULL); + g_object_set (WID ("current-password-label"), "sensitive", !state, NULL); + g_object_set (WID ("authenticate-button"), "sensitive", !state, NULL); + + /* Widgets which require authentication to be accessible */ + g_object_set (pdialog->new_password, "sensitive", state, NULL); + g_object_set (pdialog->retyped_password, "sensitive", state, NULL); + g_object_set (WID ("new-password-label"), "sensitive", state, NULL); + g_object_set (WID ("retyped-password-label"), "sensitive", state, NULL); + + if (!state) { + /* Disable change-password-button when in not-authenticated state */ + g_object_set (WID ("change-password-button"), "sensitive", FALSE, NULL); + } + + pdialog->authenticated = state; + + if (state) { + /* Authenticated state */ + + /* Focus new password */ + gtk_widget_grab_focus (GTK_WIDGET (pdialog->new_password)); + + /* Set open lock image */ + gtk_image_set_from_icon_name (GTK_IMAGE (pdialog->dialog_image), "changes-allow", GTK_ICON_SIZE_DIALOG); + } else { + /* Not authenticated state */ + + /* Focus current password */ + gtk_widget_grab_focus (GTK_WIDGET (pdialog->current_password)); + + /* Set closed lock image */ + gtk_image_set_from_icon_name (GTK_IMAGE (pdialog->dialog_image), "changes-prevent", GTK_ICON_SIZE_DIALOG); + } +} + +/* Set status field message */ +static void +passdlg_set_status (PasswordDialog *pdialog, gchar *msg) +{ + g_object_set (pdialog->status_label, "label", msg, NULL); +} + +/* Clear dialog (except the status message) */ +static void +passdlg_clear (PasswordDialog *pdialog) +{ + /* Set non-authenticated state */ + passdlg_set_auth_state (pdialog, FALSE); + + /* Clear password entries */ + gtk_entry_set_text (pdialog->current_password, ""); + gtk_entry_set_text (pdialog->new_password, ""); + gtk_entry_set_text (pdialog->retyped_password, ""); +} + +/* Start backend and handle errors + * If backend is already running, stop it + * If an error occurs, show error dialog */ +static gboolean +passdlg_spawn_passwd (PasswordDialog *pdialog) +{ + GError *error = NULL; + gchar *details; + + /* If backend is running, stop it */ + stop_passwd (pdialog); + + /* Spawn backend */ + if (!spawn_passwd (pdialog, &error)) { + GtkWidget *parent = GTK_WIDGET (gtk_builder_get_object (pdialog->ui, "change-password")); + + /* translators: Unable to launch <program>: <error message> */ + details = g_strdup_printf (_("Unable to launch %s: %s"), + "/usr/bin/passwd", error->message); + + passdlg_error_dialog (GTK_WINDOW (parent), + _("Unable to launch backend"), + _("A system error has occurred"), + details); + + g_free (details); + g_error_free (error); + + return FALSE; + } + + return TRUE; +} + +/* Called when the "Authenticate" button is clicked */ +static void +passdlg_authenticate (GtkButton *button, PasswordDialog *pdialog) +{ + /* Set busy as this can be a long process */ + passdlg_set_busy (pdialog, TRUE); + + /* Update status message */ + passdlg_set_status (pdialog, _("Checking password...")); + + /* Spawn backend */ + if (!passdlg_spawn_passwd (pdialog)) { + passdlg_set_busy (pdialog, FALSE); + return; + } + + authenticate (pdialog); + + /* Our IO watcher should now handle the rest */ +} + +/* Validate passwords + * Returns: + * PASSDLG_ERROR_NONE (0) if passwords are valid + * PASSDLG_ERROR_NEW_PASSWORD_EMPTY + * PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY + * PASSDLG_ERROR_PASSWORDS_NOT_EQUAL + */ +static guint +passdlg_validate_passwords (PasswordDialog *pdialog) +{ + GtkBuilder *dialog; + const gchar *new_password, *retyped_password; + glong nlen, rlen; + + dialog = pdialog->ui; + + new_password = gtk_entry_get_text (pdialog->new_password); + retyped_password = gtk_entry_get_text (pdialog->retyped_password); + + nlen = g_utf8_strlen (new_password, -1); + rlen = g_utf8_strlen (retyped_password, -1); + + if (nlen == 0) { + /* New password empty */ + return PASSDLG_ERROR_NEW_PASSWORD_EMPTY; + } + + if (rlen == 0) { + /* Retyped password empty */ + return PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY; + } + + if (nlen != rlen || strncmp (new_password, retyped_password, nlen) != 0) { + /* Passwords not equal */ + return PASSDLG_ERROR_PASSWORDS_NOT_EQUAL; + } + + /* Success */ + return PASSDLG_ERROR_NONE; +} + +/* Refresh the valid password UI state, i.e. re-validate + * and enable/disable the Change Password button. + * Returns: Return value of passdlg_validate_passwords */ +static guint +passdlg_refresh_password_state (PasswordDialog *pdialog) +{ + GtkBuilder *dialog; + guint ret; + gboolean valid = FALSE; + + dialog = pdialog->ui; + + ret = passdlg_validate_passwords (pdialog); + + if (ret == PASSDLG_ERROR_NONE) { + valid = TRUE; + } + + g_object_set (WID ("change-password-button"), "sensitive", valid, NULL); + + return ret; +} + +/* Called whenever any of the new password fields have changed */ +static void +passdlg_check_password (GtkEntry *entry, PasswordDialog *pdialog) +{ + guint ret; + + ret = passdlg_refresh_password_state (pdialog); + + switch (ret) { + case PASSDLG_ERROR_NONE: + passdlg_set_status (pdialog, _("Click <b>Change password</b> to change your password.")); + break; + case PASSDLG_ERROR_NEW_PASSWORD_EMPTY: + passdlg_set_status (pdialog, _("Please type your password in the <b>New password</b> field.")); + break; + case PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY: + passdlg_set_status (pdialog, _("Please type your password again in the <b>Retype new password</b> field.")); + break; + case PASSDLG_ERROR_PASSWORDS_NOT_EQUAL: + passdlg_set_status (pdialog, _("The two passwords are not equal.")); + break; + default: + g_warning ("Unknown passdlg_check_password error: %d", ret); + break; + } +} + +/* 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; +} + +/* Activates (moves focus or activates) widget w */ +static void +passdlg_activate (GtkEntry *entry, GtkWidget *w) +{ + if (GTK_IS_BUTTON (w)) { + gtk_widget_activate (w); + } else { + gtk_widget_grab_focus (w); + } +} + +/* Initialize password dialog */ +static void +passdlg_init (PasswordDialog *pdialog, GtkWindow *parent) +{ + GtkBuilder *dialog; + GtkWidget *wpassdlg; + GtkAccelGroup *group; + + /* Initialize dialog */ + dialog = gtk_builder_new (); + gtk_builder_add_from_file (dialog, MATECC_UI_DIR "/mate-about-me-password.ui", NULL); + pdialog->ui = dialog; + + wpassdlg = WID ("change-password"); + capplet_set_icon (wpassdlg, "user-info"); + + group = gtk_accel_group_new (); + + /* + * Initialize backend + */ + + /* Initialize backend_pid. -1 means the backend is not running */ + pdialog->backend_pid = -1; + + /* Initialize IO Channels */ + pdialog->backend_stdin = NULL; + pdialog->backend_stdout = NULL; + + /* Initialize write queue */ + pdialog->backend_stdin_queue = g_queue_new (); + + /* Initialize watchers */ + pdialog->backend_child_watch_id = 0; + pdialog->backend_stdout_watch_id = 0; + + /* Initialize backend state */ + pdialog->backend_state = PASSWD_STATE_NONE; + + /* + * Initialize UI + */ + + /* Initialize pdialog widgets */ + pdialog->current_password = GTK_ENTRY (WID ("current-password")); + pdialog->new_password = GTK_ENTRY (WID ("new-password")); + pdialog->retyped_password = GTK_ENTRY (WID ("retyped-password")); + pdialog->dialog_image = GTK_IMAGE (WID ("dialog-image")); + pdialog->status_label = GTK_LABEL (WID ("status-label")); + + /* Initialize accelerators */ + gtk_widget_add_accelerator (GTK_WIDGET (pdialog->current_password), + "activate", group, + GDK_Return, 0, + 0); + + gtk_widget_add_accelerator (GTK_WIDGET (pdialog->new_password), + "activate", group, + GDK_Return, 0, + 0); + + /* Activate authenticate-button when enter is pressed in current-password */ + g_signal_connect (G_OBJECT (pdialog->current_password), "activate", + G_CALLBACK (passdlg_activate), WID ("authenticate-button")); + + /* Activate retyped-password when enter is pressed in new-password */ + g_signal_connect (G_OBJECT (pdialog->new_password), "activate", + G_CALLBACK (passdlg_activate), pdialog->retyped_password); + + /* Clear status message */ + passdlg_set_status (pdialog, ""); + + /* Set non-authenticated state */ + passdlg_set_auth_state (pdialog, FALSE); + + /* Connect signal handlers */ + g_signal_connect (G_OBJECT (WID ("authenticate-button")), "clicked", + G_CALLBACK (passdlg_authenticate), pdialog); + + /* Verify new passwords on-the-fly */ + g_signal_connect (G_OBJECT (WID ("new-password")), "changed", + G_CALLBACK (passdlg_check_password), pdialog); + g_signal_connect (G_OBJECT (WID ("retyped-password")), "changed", + G_CALLBACK (passdlg_check_password), pdialog); + + /* Set misc dialog properties */ + gtk_window_set_resizable (GTK_WINDOW (wpassdlg), FALSE); + gtk_window_set_transient_for (GTK_WINDOW (wpassdlg), GTK_WINDOW (parent)); +} + +/* Main */ +void +mate_about_me_password (GtkWindow *parent) +{ + PasswordDialog *pdialog; + GtkBuilder *dialog; + GtkWidget *wpassdlg; + + gint result; + gboolean response; + + /* Initialize dialog */ + pdialog = g_new0 (PasswordDialog, 1); + passdlg_init (pdialog, parent); + + dialog = pdialog->ui; + wpassdlg = WID ("change-password"); + + /* Go! */ + gtk_widget_show_all (wpassdlg); + + do { + result = gtk_dialog_run (GTK_DIALOG (wpassdlg)); + response = passdlg_process_response (pdialog, result); + } while (response); + + /* Clean up */ + stop_passwd (pdialog); + gtk_widget_destroy (wpassdlg); + g_queue_free (pdialog->backend_stdin_queue); + g_object_unref (dialog); + g_free (pdialog); +} |