Logo Search packages:      
Sourcecode: control-center version File versions  Download package

gnome-about-me-password.c

/* gnome-about-me.c
 * Copyright (C) 2002 Diego Gonzalez
 * Copyright (C) 2006 Johannes H. Jensen
 *
 * Written by: Diego Gonzalez <diego@pemas.net>
 * Modified by: Johannes H. Jensen <joh@deworks.net>
 *
 * 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 Gnome-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;

00056 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 (gnome_about_me_password_error_quark())

GQuark gnome_about_me_password_error_quark(void)
{
      static GQuark q = 0;

      if (q == 0) {
            q = g_quark_from_static_string("gnome_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",
                                            "simple",
                                            "similar",
                                            "case",
                                            "wrapped",
                                            "recovered",
                                            "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, "simple") != 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, "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, "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 (toplevel->window, 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_file (GTK_IMAGE (pdialog->dialog_image), GNOMECC_PIXMAP_DIR "/gnome-about-me-lock-open.png");
      } else {
            /* Not authenticated state */

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

            /* Set closed lock image */
            gtk_image_set_from_file (GTK_IMAGE (pdialog->dialog_image), GNOMECC_PIXMAP_DIR "/gnome-about-me-lock.png");
      }
}

/* 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, GNOMECC_UI_DIR "/gnome-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
gnome_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);
}

Generated by  Doxygen 1.6.0   Back to index