+ free (si->unlock_typeahead);
+ si->unlock_typeahead = 0;
+}
+
+
+/**
+ * Returns a copy of the input string with trailing whitespace removed.
+ * Whitespace is anything considered so by isspace().
+ * It is safe to call this with NULL, in which case NULL will be returned.
+ * The returned string (if not NULL) should be freed by the caller with free().
+ */
+static char *
+remove_trailing_whitespace(const char *str)
+{
+ size_t len;
+ char *newstr, *chr;
+
+ if (!str)
+ return NULL;
+
+ len = strlen(str);
+
+ newstr = malloc(len + 1);
+ if (!newstr)
+ return NULL;
+
+ (void) strcpy(newstr, str);
+ chr = newstr + len;
+ while (isspace(*--chr) && chr >= newstr)
+ *chr = '\0';
+
+ return newstr;
+}
+
+
+/*
+ * The authentication conversation function.
+ * Like a PAM conversation function, this accepts multiple messages in a single
+ * round. It then splits them into individual messages for display on the
+ * passwd dialog. A message sequence of info or error followed by a prompt will
+ * be reduced into a single dialog window.
+ *
+ * Returns 0 on success or -1 if some problem occurred (cancelled, OOM, etc.)
+ */
+int
+gui_auth_conv(int num_msg,
+ const struct auth_message auth_msgs[],
+ struct auth_response **resp,
+ saver_info *si)
+{
+ int i;
+ const char *info_msg, *prompt;
+ struct auth_response *responses;
+
+ if (si->unlock_state == ul_cancel ||
+ si->unlock_state == ul_time)
+ /* If we've already cancelled or timed out in this PAM conversation,
+ don't prompt again even if PAM asks us to! */
+ return -1;
+
+ if (!(responses = calloc(num_msg, sizeof(struct auth_response))))
+ goto fail;
+
+ for (i = 0; i < num_msg; ++i)
+ {
+ info_msg = prompt = NULL;
+
+ /* See if there is a following message that can be shown at the same
+ * time */
+ if (auth_msgs[i].type == AUTH_MSGTYPE_INFO
+ && i+1 < num_msg
+ && ( auth_msgs[i+1].type == AUTH_MSGTYPE_PROMPT_NOECHO
+ || auth_msgs[i+1].type == AUTH_MSGTYPE_PROMPT_ECHO)
+ )
+ {
+ info_msg = auth_msgs[i].msg;
+ prompt = auth_msgs[++i].msg;
+ }
+ else
+ {
+ if ( auth_msgs[i].type == AUTH_MSGTYPE_INFO
+ || auth_msgs[i].type == AUTH_MSGTYPE_ERROR)
+ info_msg = auth_msgs[i].msg;
+ else
+ prompt = auth_msgs[i].msg;
+ }
+
+ {
+ char *info_msg_trimmed, *prompt_trimmed;
+
+ /* Trailing whitespace looks bad in a GUI */
+ info_msg_trimmed = remove_trailing_whitespace(info_msg);
+ prompt_trimmed = remove_trailing_whitespace(prompt);
+
+ if (make_passwd_window(si, info_msg_trimmed, prompt_trimmed,
+ auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_ECHO
+ ? True : False)
+ < 0)
+ goto fail;
+
+ if (info_msg_trimmed)
+ free(info_msg_trimmed);
+
+ if (prompt_trimmed)
+ free(prompt_trimmed);
+ }
+
+ compose_status = calloc (1, sizeof (*compose_status));
+ if (!compose_status)
+ goto fail;
+
+ si->unlock_state = ul_read;
+
+ handle_typeahead (si);
+ passwd_event_loop (si);
+
+ if (si->unlock_state == ul_cancel)
+ goto fail;
+
+ responses[i].response = strdup(si->pw_data->typed_passwd);
+
+ /* Cache the first response to a PROMPT_NOECHO to save prompting for
+ * each auth mechanism. */
+ if (si->cached_passwd == NULL &&
+ auth_msgs[i].type == AUTH_MSGTYPE_PROMPT_NOECHO)
+ si->cached_passwd = strdup(responses[i].response);
+
+ free (compose_status);
+ compose_status = 0;
+ }
+
+ *resp = responses;
+
+ return (si->unlock_state == ul_finished) ? 0 : -1;
+
+fail:
+ if (compose_status)
+ free (compose_status);
+ compose_status = 0;
+
+ if (responses)
+ {
+ for (i = 0; i < num_msg; ++i)
+ if (responses[i].response)
+ free (responses[i].response);
+ free (responses);
+ }
+
+ return -1;
+}
+
+
+void
+auth_finished_cb (saver_info *si)
+{
+ char buf[1024];
+ const char *s;
+
+ /* If we have something to say, put the dialog back up for a few seconds
+ to display it. Otherwise, don't bother.
+ */
+
+ if (si->unlock_state == ul_fail && /* failed with caps lock on */
+ si->pw_data && si->pw_data->caps_p)
+ s = "Authentication failed (Caps Lock?)";
+ else if (si->unlock_state == ul_fail) /* failed without caps lock */
+ s = "Authentication failed!";
+ else if (si->unlock_state == ul_success && /* good, but report failures */
+ si->unlock_failures > 0)
+ {
+ if (si->unlock_failures == 1)
+ s = "There has been\n1 failed login attempt.";
+ else
+ {
+ sprintf (buf, "There have been\n%d failed login attempts.",
+ si->unlock_failures);
+ s = buf;
+ }
+ si->unlock_failures = 0;
+ }
+ else /* good, with no failures, */
+ goto END; /* or timeout, or cancel. */
+
+ make_passwd_window (si, s, NULL, True);
+ XSync (si->dpy, False);
+
+ {
+ int secs = 4;
+ time_t start = time ((time_t *) 0);
+ XEvent event;
+ while (time ((time_t *) 0) < start + secs)
+ if (XPending (si->dpy))
+ {
+ XNextEvent (si->dpy, &event);
+ if (event.xany.window == si->passwd_dialog &&
+ event.xany.type == Expose)
+ draw_passwd_window (si);
+ else if (event.xany.type == ButtonPress ||
+ event.xany.type == KeyPress)
+ break;
+ XSync (si->dpy, False);
+ }
+ else
+ usleep (250000); /* 1/4 second */
+ }
+
+ END:
+ if (si->pw_data)