/* mate-about-me.c * Copyright (C) 2002 Diego Gonzalez * Copyright (C) 2006 Johannes H. Jensen * * Written by: Diego Gonzalez * Modified by: Johannes H. Jensen * * 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., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301, USA. * * Parts of this code come from Mate-System-Tools. */ #ifdef HAVE_CONFIG_H # include #endif /* Are all of these needed? */ #include #include #include #include #include #include #include #ifdef __sun #include #include #endif #include "capplet-util.h" #include "mate-about-me-password.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; /* We need to save message from libpwquality globaly */ gchar *ext_msg; /* Whether we have authenticated */ gboolean authenticated; /* Communication with the passwd program */ GPid backend_pid; GIOChannel *backend_stdin; GIOChannel *backend_stdout; GIOChannel *backend_stderr; 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) */ guint backend_stderr_watch_id; /* g_io_add_watch (stderr) */ /* State of the passwd program */ PasswdState backend_state; } PasswordDialog; /* Buffer size for backend output (64 is too small to store message from libpwquality)*/ #define BUFSIZE 128 /* * Error handling {{ */ #define PASSDLG_ERROR (mate_about_me_password_error_quark()) static 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 occurred */ free_passwd_resources (pdialog); return FALSE; } /* Initialize ext_msg with NULL */ pdialog->ext_msg = NULL; /* Open IO Channels */ pdialog->backend_stdin = g_io_channel_unix_new (my_stdin); pdialog->backend_stdout = g_io_channel_unix_new (my_stdout); pdialog->backend_stderr = g_io_channel_unix_new (my_stderr); /* 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_encoding (pdialog->backend_stderr, 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 || g_io_channel_set_flags (pdialog->backend_stderr, 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); g_io_channel_set_buffered (pdialog->backend_stderr, 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); pdialog->backend_stderr_watch_id = g_io_add_watch (pdialog->backend_stderr, 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; } if (pdialog->backend_stderr != NULL) { if (g_io_channel_shutdown (pdialog->backend_stderr, TRUE, &error) != G_IO_STATUS_NORMAL) { g_warning (_("Could not shutdown backend_stderr IO channel: %s"), error->message); g_error_free (error); error = NULL; } g_io_channel_unref (pdialog->backend_stderr); pdialog->backend_stderr = 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; } if (pdialog->backend_stderr_watch_id != 0) { g_source_remove (pdialog->backend_stderr_watch_id); pdialog->backend_stderr_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, gboolean retry) { 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); if (!retry) { 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 */ gboolean reinit = FALSE; /* Initialize buffer */ if (str == NULL) { str = g_string_new (""); } if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &error) != G_IO_STATUS_NORMAL) { if (error) { 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, "New password: ") != NULL) { /* Authentication successful */ authenticated_user (pdialog, FALSE); } 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, " new password: ", NULL)) { /* Pop retyped password from queue and into IO channel */ io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin); reinit = TRUE; } else if (is_string_complete (str->str, "New password: ", NULL)) { authenticated_user(pdialog, TRUE); reinit = TRUE; } /* Advance to next state */ pdialog->backend_state = PASSWD_STATE_RETYPE; 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", "rotated", "error", "BAD PASSWORD", 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); } else if (g_strrstr (str->str, "BAD PASSWORD") != NULL) { /* Actual error description from libpwquality is located in the first string */ msg = g_strdup("Password hasn't changed!"); pdialog->ext_msg = g_strdup(g_strsplit(str->str, "\n", 2)[0]); stop_passwd(pdialog); } } /* child_watch_cb should clean up for us now */ } reinit = TRUE; if (msg != NULL) { /* An error occurred! */ passdlg_clear(pdialog); passdlg_set_status (pdialog, 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. */ if (pdialog->ext_msg != NULL) { passdlg_clear(pdialog); passdlg_set_status (pdialog, g_strconcat(msg, "\n", pdialog->ext_msg, NULL)); g_free (pdialog->ext_msg); } pdialog->backend_state = PASSWD_STATE_ERR; /* Clean backen_stdin_queue if error occured */ #if GLIB_CHECK_VERSION(2, 60, 0) g_queue_clear_full (pdialog->backend_stdin_queue, g_free); #else /* TODO: Remove this if/when GLib compatibility gets dropped. */ g_queue_foreach (pdialog->backend_stdin_queue, (GFunc) g_free, NULL); g_queue_clear (pdialog->backend_stdin_queue); #endif g_free (msg); } 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, FALSE); /* 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) { g_object_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, "%s", msg); if (title) gtk_window_set_title (GTK_WINDOW (dialog), title); if (details) gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", 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 : */ 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) { const gchar *new_password, *retyped_password; glong nlen, rlen; 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 Change password to change your password.")); break; case PASSDLG_ERROR_NEW_PASSWORD_EMPTY: passdlg_set_status (pdialog, _("Please type your password in the New password field.")); break; case PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY: passdlg_set_status (pdialog, _("Please type your password again in the Retype new password 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 occurred 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 * has occurred 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; GError *error = NULL; /* Initialize dialog */ dialog = gtk_builder_new (); if (gtk_builder_add_from_resource (dialog, "/org/mate/mcc/am/mate-about-me-password.ui", &error) == 0) { g_warning ("Could not parse UI definition: %s", error->message); g_error_free (error); } 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; pdialog->backend_stderr = 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; pdialog->backend_stderr_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_KEY_Return, 0, 0); gtk_widget_add_accelerator (GTK_WIDGET (pdialog->new_password), "activate", group, GDK_KEY_Return, 0, 0); /* Activate authenticate-button when enter is pressed in current-password */ g_signal_connect (pdialog->current_password, "activate", G_CALLBACK (passdlg_activate), WID ("authenticate-button")); /* Activate retyped-password when enter is pressed in new-password */ g_signal_connect (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 (gtk_builder_get_object (dialog, "authenticate-button"), "clicked", G_CALLBACK (passdlg_authenticate), pdialog); /* Verify new passwords on-the-fly */ g_signal_connect (gtk_builder_get_object (dialog, "new-password"), "changed", G_CALLBACK (passdlg_check_password), pdialog); g_signal_connect (gtk_builder_get_object (dialog, "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); }