gboolean passwd_change_password (PasswdHandler *passwd_handler, const char *new_password, PasswdCallback cb, const gpointer user_data) { GError *error = NULL; passwd_handler->changing_password = TRUE; passwd_handler->new_password = new_password; passwd_handler->chpasswd_cb = cb; passwd_handler->chpasswd_cb_data = user_data; /* Stop passwd if an error occured and it is still running */ if (passwd_handler->backend_state == PASSWD_STATE_ERR) { /* Stop passwd, free resources */ stop_passwd (passwd_handler); } /* Check that the backend is still running, or that an error * has occured but it has not yet exited */ if (passwd_handler->backend_pid == -1) { /* If it is not, re-run authentication */ /* Spawn backend */ stop_passwd (passwd_handler); if (!spawn_passwd (passwd_handler, &error)) { g_warning ("%s", error->message); g_error_free (error); return FALSE; } /* Add current and new passwords to queue */ authenticate (passwd_handler); update_password (passwd_handler); } else { /* Only add new passwords to queue */ update_password (passwd_handler); } /* Pop new password through the backend. * If user has no password, popping the queue would output current * password, while 'passwd' is waiting for the new one. So wait for * io_watch_stdout() to remove current password from the queue, * and output the new one for us. */ if (passwd_handler->current_password) io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin); /* Our IO watcher should now handle the rest */ return TRUE; }
/* Called when the "Change password" dialog-button is clicked * Returns: TRUE if we want to keep the dialog running, FALSE otherwise */ static gboolean passdlg_process_response (PasswordDialog *pdialog, gint response_id) { if (response_id == GTK_RESPONSE_OK) { /* Set busy as this can be a long process */ passdlg_set_busy (pdialog, TRUE); /* Stop passwd if an error occured and it is still running */ if (pdialog->backend_state == PASSWD_STATE_ERR) { /* Stop passwd, free resources */ stop_passwd (pdialog); } /* Check that the backend is still running, or that an error * hass occured but it has not yet exited */ if (pdialog->backend_pid == -1) { /* If it is not, re-run authentication */ /* Spawn backend */ if (!passdlg_spawn_passwd (pdialog)) { return TRUE; } /* Add current and new passwords to queue */ authenticate (pdialog); update_password (pdialog); } else { /* Only add new passwords to queue */ update_password (pdialog); /* Pop new password through the backend */ io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin); } /* Our IO watcher should now handle the rest */ /* Keep the dialog running */ return TRUE; } return FALSE; }
/* Authentication attempt succeeded. Update the GUI accordingly. */ static void authenticated_user (PasswordDialog *pdialog) { pdialog->backend_state = PASSWD_STATE_NEW; if (pdialog->authenticated) { /* This is a re-authentication * It succeeded, so pop our new password from the queue */ io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin); } /* Update UI state */ passdlg_set_auth_state (pdialog, TRUE); passdlg_set_status (pdialog, _("Authenticated!")); /* Check to see if the passwords are valid * (They might be non-empty if the user had to re-authenticate, * and thus we need to enable the change-password-button) */ passdlg_refresh_password_state (pdialog); }
/* * IO watcher for stdout, called whenever there is data to read from the backend. * This is where most of the actual IO handling happens. */ static gboolean io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswordDialog *pdialog) { static GString *str = NULL; /* Persistent buffer */ gchar buf[BUFSIZE]; /* Temporary buffer */ gsize bytes_read; GError *error = NULL; gchar *msg = NULL; /* Status error message */ GtkBuilder *dialog; gboolean reinit = FALSE; /* Initialize buffer */ if (str == NULL) { str = g_string_new (""); } dialog = pdialog->ui; if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &error) != G_IO_STATUS_NORMAL) { g_warning ("IO Channel read error: %s", error->message); g_error_free (error); return TRUE; } str = g_string_append_len (str, buf, bytes_read); /* In which state is the backend? */ switch (pdialog->backend_state) { case PASSWD_STATE_AUTH: /* Passwd is asking for our current password */ if (is_string_complete (str->str, "assword: ", "failure", "wrong", "error", NULL)) { /* Which response did we get? */ passdlg_set_busy (pdialog, FALSE); if (g_strrstr (str->str, "assword: ") != NULL) { /* Authentication successful */ authenticated_user (pdialog); } else { /* Authentication failed */ if (pdialog->authenticated) { /* This is a re-auth, and it failed. * The password must have been changed in the meantime! * Ask the user to re-authenticate */ /* Update status message and auth state */ passdlg_set_status (pdialog, _("Your password has been changed since you initially authenticated! Please re-authenticate.")); } else { passdlg_set_status (pdialog, _("That password was incorrect.")); } /* Focus current password */ gtk_widget_grab_focus (GTK_WIDGET (pdialog->current_password)); } reinit = TRUE; } break; case PASSWD_STATE_NEW: /* Passwd is asking for our new password */ if (is_string_complete (str->str, "assword: ", NULL)) { /* Advance to next state */ pdialog->backend_state = PASSWD_STATE_RETYPE; /* Pop retyped password from queue and into IO channel */ io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin); reinit = TRUE; } break; case PASSWD_STATE_RETYPE: /* Passwd is asking for our retyped new password */ if (is_string_complete (str->str, "successfully", "short", "longer", "palindrome", "dictionary", "simpl", /* catches both simple and simplistic */ "similar", "different", "case", "wrapped", "recovered", "recent" "unchanged", "match", "1 numeric or special", "failure", NULL)) { /* What response did we get? */ passdlg_set_busy (pdialog, FALSE); if (g_strrstr (str->str, "successfully") != NULL) { /* Hooray! */ passdlg_clear (pdialog); passdlg_set_status (pdialog, _("Your password has been changed.")); } else { /* Ohnoes! */ /* Focus new password */ gtk_widget_grab_focus (GTK_WIDGET (pdialog->new_password)); if (g_strrstr (str->str, "recovered") != NULL) { /* What does this indicate? * "Authentication information cannot be recovered?" from libpam? */ msg = g_strdup_printf (_("System error: %s."), str->str); } else if (g_strrstr (str->str, "short") != NULL || g_strrstr (str->str, "longer") != NULL) { msg = g_strdup (_("The password is too short.")); } else if (g_strrstr (str->str, "palindrome") != NULL || g_strrstr (str->str, "simpl") != NULL || g_strrstr (str->str, "dictionary") != NULL) { msg = g_strdup (_("The password is too simple.")); } else if (g_strrstr (str->str, "similar") != NULL || g_strrstr (str->str, "different") != NULL || g_strrstr (str->str, "case") != NULL || g_strrstr (str->str, "wrapped") != NULL) { msg = g_strdup (_("The old and new passwords are too similar.")); } else if (g_strrstr (str->str, "1 numeric or special") != NULL) { msg = g_strdup (_("The new password must contain numeric or special character(s).")); } else if (g_strrstr (str->str, "unchanged") != NULL || g_strrstr (str->str, "match") != NULL) { msg = g_strdup (_("The old and new passwords are the same.")); } else if (g_strrstr (str->str, "recent") != NULL) { msg = g_strdup (_("The new password has already been used recently.")); } else if (g_strrstr (str->str, "failure") != NULL) { /* Authentication failure */ msg = g_strdup (_("Your password has been changed since you initially authenticated! Please re-authenticate.")); passdlg_set_auth_state (pdialog, FALSE); } } reinit = TRUE; if (msg != NULL) { /* An error occured! */ passdlg_set_status (pdialog, msg); g_free (msg); /* At this point, passwd might have exited, in which case * child_watch_cb should clean up for us and remove this watcher. * On some error conditions though, passwd just re-prompts us * for our new password. */ pdialog->backend_state = PASSWD_STATE_ERR; } /* child_watch_cb should clean up for us now */ } break; case PASSWD_STATE_NONE: /* Passwd is not asking for anything yet */ if (is_string_complete (str->str, "assword: ", NULL)) { /* If the user does not have a password set, * passwd will immediately ask for the new password, * so skip the AUTH phase */ if (is_string_complete (str->str, "new", "New", NULL)) { gchar *pw; pdialog->backend_state = PASSWD_STATE_NEW; passdlg_set_busy (pdialog, FALSE); authenticated_user (pdialog); /* since passwd didn't ask for our old password * in this case, simply remove it from the queue */ pw = g_queue_pop_head (pdialog->backend_stdin_queue); g_free (pw); } else { pdialog->backend_state = PASSWD_STATE_AUTH; /* Pop the IO queue, i.e. send current password */ io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin); } reinit = TRUE; } break; default: /* Passwd has returned an error */ reinit = TRUE; break; } if (reinit) { g_string_free (str, TRUE); str = NULL; } /* Continue calling us */ return TRUE; }
/* * IO watcher for stdout, called whenever there is data to read from the backend. * This is where most of the actual IO handling happens. */ static gboolean io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler) { static GString *str = NULL; /* Persistent buffer */ gchar buf[BUFSIZE]; /* Temporary buffer */ gsize bytes_read; GError *gio_error = NULL; /* Error returned by functions */ GError *error = NULL; /* Error sent to callbacks */ gboolean reinit = FALSE; /* Initialize buffer */ if (str == NULL) { str = g_string_new (""); } if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &gio_error) != G_IO_STATUS_NORMAL) { g_warning ("IO Channel read error: %s", gio_error->message); g_error_free (gio_error); return TRUE; } str = g_string_append_len (str, buf, bytes_read); /* In which state is the backend? */ switch (passwd_handler->backend_state) { case PASSWD_STATE_AUTH: /* Passwd is asking for our current password */ if (is_string_complete (str->str, "assword: ", "failure", "wrong", "error", NULL)) { if (strstr (str->str, "assword: ") != NULL) { /* Authentication successful */ passwd_handler->backend_state = PASSWD_STATE_NEW; /* Trigger callback to update authentication status */ if (passwd_handler->auth_cb) passwd_handler->auth_cb (passwd_handler, NULL, passwd_handler->auth_cb_data); } else { /* Authentication failed */ error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED, _("Authentication failed")); passwd_handler->changing_password = FALSE; /* This error can happen both while authenticating or while changing password: * if chpasswd_cb is set, this means we're already changing password */ if (passwd_handler->chpasswd_cb) passwd_handler->chpasswd_cb (passwd_handler, error, passwd_handler->chpasswd_cb_data); else if (passwd_handler->auth_cb) passwd_handler->auth_cb (passwd_handler, error, passwd_handler->auth_cb_data); g_error_free (error); } reinit = TRUE; } break; case PASSWD_STATE_NEW: /* Passwd is asking for our new password */ if (is_string_complete (str->str, "assword: ", NULL)) { /* Advance to next state */ passwd_handler->backend_state = PASSWD_STATE_RETYPE; /* Pop retyped password from queue and into IO channel */ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin); reinit = TRUE; } break; case PASSWD_STATE_RETYPE: /* Passwd is asking for our retyped new password */ if (is_string_complete (str->str, "successfully", "short", "longer", "palindrome", "dictionary", "simple", "simplistic", "similar", "case", "different", "wrapped", "recovered", "recent", "unchanged", "match", "1 numeric or special", "failure", "DIFFERENT", "BAD PASSWORD", NULL)) { if (strstr (str->str, "successfully") != NULL) { /* Hooray! */ passwd_handler->backend_state = PASSWD_STATE_DONE; /* Trigger callback to update status */ if (passwd_handler->chpasswd_cb) passwd_handler->chpasswd_cb (passwd_handler, NULL, passwd_handler->chpasswd_cb_data); } else { /* Ohnoes! */ if (strstr (str->str, "recovered") != NULL) { /* What does this indicate? * "Authentication information cannot be recovered?" from libpam? */ error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN, str->str); } else if (strstr (str->str, "short") != NULL || strstr (str->str, "longer") != NULL) { error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, _("The new password is too short")); } else if (strstr (str->str, "palindrome") != NULL || strstr (str->str, "simple") != NULL || strstr (str->str, "simplistic") != NULL || strstr (str->str, "dictionary") != NULL) { error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, _("The new password is too simple")); } else if (strstr (str->str, "similar") != NULL || strstr (str->str, "different") != NULL || strstr (str->str, "case") != NULL || strstr (str->str, "wrapped") != NULL) { error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, _("The old and new passwords are too similar")); } else if (strstr (str->str, "recent") != NULL) { error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, _("The new password has already been used recently.")); } else if (strstr (str->str, "1 numeric or special") != NULL) { error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, _("The new password must contain numeric or special characters")); } else if (strstr (str->str, "unchanged") != NULL || strstr (str->str, "match") != NULL) { error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, _("The old and new passwords are the same")); } else if (strstr (str->str, "failure") != NULL) { /* Authentication failure */ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED, _("Your password has been changed since you initially authenticated!")); } else if (strstr (str->str, "DIFFERENT")) { error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED, _("The new password does not contain enough different characters")); } else { error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN, _("Unknown error")); } /* At this point, passwd might have exited, in which case * child_watch_cb should clean up for us and remove this watcher. * On some error conditions though, passwd just re-prompts us * for our new password. */ passwd_handler->backend_state = PASSWD_STATE_ERR; passwd_handler->changing_password = FALSE; /* Trigger callback to update status */ if (passwd_handler->chpasswd_cb) passwd_handler->chpasswd_cb (passwd_handler, error, passwd_handler->chpasswd_cb_data); g_error_free (error); } reinit = TRUE; /* child_watch_cb should clean up for us now */ } break; case PASSWD_STATE_NONE: /* Passwd is not asking for anything yet */ if (is_string_complete (str->str, "assword: ", NULL)) { /* If the user does not have a password set, * passwd will immediately ask for the new password, * so skip the AUTH phase */ if (is_string_complete (str->str, "new", "New", NULL)) { gchar *pw; passwd_handler->backend_state = PASSWD_STATE_NEW; /* since passwd didn't ask for our old password * in this case, simply remove it from the queue */ pw = g_queue_pop_head (passwd_handler->backend_stdin_queue); g_free (pw); /* Pop the IO queue, i.e. send new password */ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin); } else { passwd_handler->backend_state = PASSWD_STATE_AUTH; /* Pop the IO queue, i.e. send current password */ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin); } reinit = TRUE; } break; default: /* Passwd has returned an error */ reinit = TRUE; break; } if (reinit) { g_string_free (str, TRUE); str = NULL; } /* Continue calling us */ return TRUE; }