<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include <X11/X.h>                  // for Atom, Success, None, GCBackground
#include <X11/Xlib.h>               // for XDrawString, XTextWidth, XFontStruct
#include <X11/extensions/XKB.h>     // for XkbUseCoreKbd, XkbGroupNamesMask
#include <X11/extensions/XKBstr.h>  // for XkbStateRec, _XkbDesc, _XkbNamesRec
#include <locale.h>                 // for NULL, setlocale, LC_CTYPE
#include <pwd.h>                    // for getpwuid, passwd
#include <security/_pam_types.h>    // for PAM_SUCCESS, pam_strerror, pam_re...
#include <security/pam_appl.h>      // for pam_end, pam_start, pam_acct_mgmt
#include <stdio.h>                  // for fprintf, stderr, LogErrno, NULL
#include <stdlib.h>                 // for mblen, exit, free, calloc, getenv
#include <string.h>                 // for memcpy, strlen, memset
#include <sys/select.h>             // for timeval, select, FD_SET, FD_ZERO
#include <time.h>                   // for time
#include <unistd.h>                 // for gethostname, getuid, read, ssize_t

#ifdef HAVE_XKB_EXT
#include <X11/XKBlib.h>  // for XkbFreeClientMap, XkbGetIndicator...
#endif

#include "../env_settings.h"      // for GetStringSetting
#include "../logging.h"           // for Log, LogErrno
#include "../mlock_page.h"        // for MLOCK_PAGE
#include "../xscreensaver_api.h"  // for ReadWindowID
#include "monitors.h"             // for Monitor, GetMonitors, IsMonitorCh...

//! The blinking interval in microseconds.
#define BLINK_INTERVAL (250 * 1000)

//! The maximum time to wait at a prompt for user input in microseconds.
#define PROMPT_TIMEOUT (5 * 60 * 1000 * 1000)

//! Length of the "paranoid password display".
#define PARANOID_PASSWORD_LENGTH 32

//! Minimum distance the cursor shall move on keypress.
#define PARANOID_PASSWORD_MIN_CHANGE 4

//! Whether password display should hide the length.
int paranoid_password = 1;

//! The X11 display.
Display *display;

//! The X11 window to draw in. Provided from $XSCREENSAVER_WINDOW.
Window window;

//! The X11 graphics context to draw with.
GC gc;

//! The font for the PAM messages.
XFontStruct *font;

//! The Black color (used as background).
unsigned long Black;

//! The White color (used as foreground).
unsigned long White;

//! Set if a conversation error has happened during the last PAM call.
static int conv_error = 0;

//! The cursor character displayed at the end of the masked password input.
static const char cursor[] = "_";

#define MAX_MONITORS 16
static int num_monitors;
static Monitor monitors[MAX_MONITORS];

int have_xkb_ext;

/*! \brief Check which modifiers are active.
 *
 * \return The current modifier mask as a string.
 */
const char *get_indicators() {
#ifdef HAVE_XKB_EXT
  static char buf[128];
  char *p;

  if (!have_xkb_ext) {
    return "";
  }

  XkbDescPtr xkb;
  xkb = XkbGetMap(display, 0, XkbUseCoreKbd);
  if (XkbGetNames(
          display,
          XkbIndicatorNamesMask | XkbGroupNamesMask | XkbSymbolsNameMask,
          xkb) != Success) {
    Log("XkbGetNames failed");
    XkbFreeClientMap(xkb, 0, True);
    return "";
  }
  XkbStateRec state;
  if (XkbGetState(display, XkbUseCoreKbd, &state) != Success) {
    Log("XkbGetState failed");
    XkbFreeClientMap(xkb, 0, True);
    return "";
  }
  unsigned int istate;
  if (XkbGetIndicatorState(display, XkbUseCoreKbd, &istate) != Success) {
    Log("XkbGetIndicatorState failed");
    XkbFreeClientMap(xkb, 0, True);
    return "";
  }

  p = buf;

  const char *word = "Keyboard: ";
  size_t n = strlen(word);
  if (n >= sizeof(buf) - (p - buf)) {
    Log("Not enough space to store intro '%s'", word);
    return "";
  }
  memcpy(p, word, n);
  p += n;

  int have_output = 0;
  Atom layouta = xkb->names->groups[state.group];  // Human-readable.
  if (layouta == None) {
    layouta = xkb->names->symbols;  // Machine-readable fallback.
  }
  if (layouta != None) {
    const char *layout = XGetAtomName(display, layouta);
    n = strlen(layout);
    if (n >= sizeof(buf) - (p - buf)) {
      Log("Not enough space to store layout name '%s'", layout);
      return "";
    }
    memcpy(p, layout, n);
    p += n;
    have_output = 1;
  }

  int i;
  for (i = 0; i < XkbNumIndicators; i++) {
    if (!(istate & (1 << i))) {
      continue;
    }
    Atom namea = xkb->names->indicators[i];
    if (namea == None) {
      continue;
    }
    if (have_output) {
      if (2 >= sizeof(buf) - (p - buf)) {
        Log("Not enough space to store another modifier name");
        break;
      }
      memcpy(p, ", ", 2);
      p += 2;
    }
    const char *name = XGetAtomName(display, namea);
    size_t n = strlen(name);
    if (n >= sizeof(buf) - (p - buf)) {
      Log("Not enough space to store modifier name '%s'", name);
      break;
    }
    memcpy(p, name, n);
    p += n;
    have_output = 1;
  }
  *p = 0;
  return have_output ? buf : "";
#else
  return "";
#endif
}

/*! \brief Display a string in the window.
 *
 * The given title and message will be displayed on all screens. In case caps
 * lock is enabled, the string's case will be inverted.
 *
 * \param title The title of the message.
 * \param str The message itself.
 */
void display_string(const char *title, const char *str) {
  static int region_x;
  static int region_y;
  static int region_w = 0;
  static int region_h = 0;

  int th = font->max_bounds.ascent + font->max_bounds.descent + 4;
  int to = font->max_bounds.ascent + 2;  // Text at to has bbox from 0 to th.

  int len_title = strlen(title);
  int tw_title = XTextWidth(font, title, len_title);

  int len_str = strlen(str);
  int tw_str = XTextWidth(font, str, len_str);

  int tw_cursor = XTextWidth(font, cursor, strlen(cursor));

  const char *indicators = get_indicators();
  int len_indicators = strlen(indicators);
  int tw_indicators = XTextWidth(font, indicators, len_indicators);

  // Compute the region we will be using, relative to cx and cy.
  if (region_w < tw_title + tw_cursor) {
    region_w = tw_title + tw_cursor;
  }
  if (region_w < tw_str + tw_cursor) {
    region_w = tw_str + tw_cursor;
  }
  if (region_w < tw_indicators + tw_cursor) {
    region_w = tw_indicators + tw_cursor;
  }
  region_x = -region_w / 2;
  region_h = 4 * th;
  region_y = -region_h / 2;

  int i;
  for (i = 0; i < num_monitors; ++i) {
    int cx = monitors[i].x + monitors[i].width / 2;
    int cy = monitors[i].y + monitors[i].height / 2;
    int sy = cy + to - th * 2;

    // Clip all following output to the bounds of this monitor.
    XRectangle rect;
    rect.x = monitors[i].x;
    rect.y = monitors[i].y;
    rect.width = monitors[i].width;
    rect.height = monitors[i].height;
    XSetClipRectangles(display, gc, 0, 0, &rect, 1, YXBanded);

    // Clear the region last written to.
    if (region_w != 0 && region_h != 0) {
      XClearArea(display, window, cx + region_x, cy + region_y, region_w,
                 region_h, False);
    }

    XDrawString(display, window, gc, cx - tw_title / 2, sy, title, len_title);

    XDrawString(display, window, gc, cx - tw_str / 2, sy + th * 2, str,
                len_str);
    XDrawString(display, window, gc, cx - tw_indicators / 2, sy + th * 3,
                indicators, len_indicators);

    // Disable clipping again.
    XSetClipMask(display, gc, None);
  }

  // Make the things just drawn appear on the screen as soon as possible.
  XFlush(display);
}

void wait_for_keypress(int seconds) {
  // Sleep for up to 1 second _or_ a key press.
  struct timeval timeout;
  timeout.tv_sec = seconds;
  timeout.tv_usec = 0;
  fd_set set;
  memset(&set, 0, sizeof(set));  // For clang-analyzer.
  FD_ZERO(&set);
  FD_SET(0, &set);
  select(1, &set, NULL, NULL, &timeout);
}

//! The size of the buffer to store the password in. Not NUL terminated.
#define PWBUF_SIZE 256

//! The size of the buffer to use for display, with space for cursor and NUL.
#define DISPLAYBUF_SIZE (PWBUF_SIZE + 2)

/*! \brief Ask a question to the user.
 *
 * \param msg The message.
 * \param response The response will be stored in a newly allocated buffer here.
 *   The caller is supposed to eventually free() it.
 * \param echo If true, the input will be shown; otherwise it will be hidden
 *   (password entry).
 * \return PAM_SUCCESS if successful, anything else otherwise.
 */
int prompt(const char *msg, char **response, int echo) {
  // Ask something. Return strdup'd string.
  struct {
    // The received X11 event.
    XEvent ev;

    // Input buffer. Not NUL-terminated.
    char pwbuf[PWBUF_SIZE];
    // Current input length.
    size_t pwlen;

    // Display buffer. If echo is 0, this will only contain asterisks, a
    // possible cursor, and be NUL-terminated.
    char displaybuf[DISPLAYBUF_SIZE];
    // Display buffer length.
    size_t displaylen;

    // The display marker changes on every input action to a value from 0 to
    // PARANOID_PASSWORD-1. It indicates where to display the "cursor".
    size_t displaymarker;

    // Character read buffer.
    char inputbuf;

    // Temporary position variables that might leak properties about the
    // password and thus are in the private struct too.
    size_t prevpos;
    size_t pos;
    int len;
  } priv;
  int blinks = 0;

  if (!echo && MLOCK_PAGE(&priv, sizeof(priv)) < 0) {
    LogErrno("mlock");
    // We continue anyway, as the user being unable to unlock the screen is
    // worse. But let's alert the user.
    display_string("Error", "Password will not be stored securely.");
    wait_for_keypress(1);
  }

  priv.pwlen = 0;
  priv.displaymarker = rand() % PARANOID_PASSWORD_LENGTH;

  int max_blinks = PROMPT_TIMEOUT / BLINK_INTERVAL;

  // Unfortunately we may have to break out of multiple loops at once here but
  // still do common cleanup work. So we have to track the return value in a
  // variable.
  int status = PAM_CONV_ERR;
  int done = 0;

  while (!done) {
    if (echo) {
      if (priv.pwlen != 0) {
        memcpy(priv.displaybuf, priv.pwbuf, priv.pwlen);
      }
      priv.displaylen = priv.pwlen;
      // Note that priv.pwlen <= sizeof(priv.pwbuf) and thus
      // priv.pwlen + 2 <= sizeof(priv.displaybuf).
      priv.displaybuf[priv.displaylen] = (blinks % 2) ? ' ' : *cursor;
      priv.displaybuf[priv.displaylen + 1] = '\0';
    } else if (paranoid_password) {
      priv.displaylen = PARANOID_PASSWORD_LENGTH;
      memset(priv.displaybuf, '_', priv.displaylen);
      priv.displaybuf[priv.pwlen ? priv.displaymarker : 0] =
          (blinks % 2) ? '|' : '-';
      priv.displaybuf[priv.displaylen] = '\0';
    } else {
      mblen(NULL, 0);
      priv.pos = priv.displaylen = 0;
      while (priv.pos < priv.pwlen) {
        ++priv.displaylen;
        // Note: this won't read past priv.pwlen.
        priv.len = mblen(priv.pwbuf + priv.pos, priv.pwlen - priv.pos);
        if (priv.len <= 0) {
          // This guarantees to "eat" one byte each step. Therefore,
          // priv.displaylen <= priv.pwlen is ensured.
          break;
        }
        priv.pos += priv.len;
      }
      memset(priv.displaybuf, '*', priv.displaylen);
      // Note that priv.pwlen <= sizeof(priv.pwbuf) and thus
      // priv.pwlen + 2 <= sizeof(priv.displaybuf).
      priv.displaybuf[priv.displaylen] = (blinks % 2) ? ' ' : *cursor;
      priv.displaybuf[priv.displaylen + 1] = '\0';
    }
    display_string(msg, priv.displaybuf);

    // Blink the cursor.
    ++blinks;
    if (blinks > max_blinks) {
      done = 1;
      break;
    }

    struct timeval timeout;
    timeout.tv_sec = BLINK_INTERVAL / 1000000;
    timeout.tv_usec = BLINK_INTERVAL % 1000000;

    while (!done) {
      fd_set set;
      memset(&set, 0, sizeof(set));  // For clang-analyzer.
      FD_ZERO(&set);
      FD_SET(0, &set);
      int nfds = select(1, &set, NULL, NULL, &timeout);
      if (nfds < 0) {
        LogErrno("select");
        done = 1;
        break;
      }
      if (nfds == 0) {
        // Blink...
        break;
      }

      // From now on, only do nonblocking selects so we update the screen ASAP.
      timeout.tv_usec = 0;

      // Force the cursor to be in visible state while typing. This also resets
      // the prompt timeout.
      blinks = 0;

      ssize_t nread = read(0, &priv.inputbuf, 1);
      if (nread <= 0) {
        Log("EOF on password input - bailing out");
        done = 1;
        break;
      }
      switch (priv.inputbuf) {
        case '\b':      // Backspace.
        case '\177': {  // Delete (note: i3lock does not handle this one).
          // Backwards skip with multibyte support.
          mblen(NULL, 0);
          priv.pos = priv.prevpos = 0;
          while (priv.pos < priv.pwlen) {
            priv.prevpos = priv.pos;
            // Note: this won't read past priv.pwlen.
            priv.len = mblen(priv.pwbuf + priv.pos, priv.pwlen - priv.pos);
            if (priv.len <= 0) {
              // This guarantees to "eat" one byte each step. Therefore,
              // this cannot loop endlessly.
              break;
            }
            priv.pos += priv.len;
          }
          if (priv.prevpos != priv.pwlen) {
            priv.displaymarker =
                (priv.displaymarker - 1 + PARANOID_PASSWORD_MIN_CHANGE +
                 rand() % (PARANOID_PASSWORD_LENGTH -
                           2 * PARANOID_PASSWORD_MIN_CHANGE)) %
                    (PARANOID_PASSWORD_LENGTH - 1) +
                1;
          }
          priv.pwlen = priv.prevpos;
          break;
        }
        case '\001':  // Ctrl-A.
          // Clearing input line on just Ctrl-A is odd - but commonly
          // requested. In most toolkits, Ctrl-A does not immediately erase but
          // almost every keypress other than arrow keys will erase afterwards.
          priv.pwlen = 0;
          break;
        case '\025':  // Ctrl-U.
          // Delete the entire input line.
          // i3lock: supports Ctrl-U but not Ctrl-A.
          // xscreensaver: supports Ctrl-U and Ctrl-X but not Ctrl-A.
          priv.pwlen = 0;
          break;
        case 0:       // Shouldn't happen.
        case '\033':  // Escape.
          done = 1;
          break;
        case '\r':  // Return.
        case '\n':  // Return.
          *response = malloc(priv.pwlen + 1);
          if (!echo && MLOCK_PAGE(*response, priv.pwlen + 1) < 0) {
            LogErrno("mlock");
            // We continue anyway, as the user being unable to unlock the screen
            // is worse. But let's alert the user of this.
            display_string("Error", "Password has not been stored securely.");
            wait_for_keypress(1);
          }
          if (priv.pwlen != 0) {
            memcpy(*response, priv.pwbuf, priv.pwlen);
          }
          (*response)[priv.pwlen] = 0;
          status = PAM_SUCCESS;
          done = 1;
          break;
        default:
          if (priv.inputbuf >= '\000' && priv.inputbuf <= '\037') {
            // Other control character. We ignore them (and specifically do not
            // update the cursor on them) to "discourage" their use in
            // passwords, as most login screens do not support them anyway.
            break;
          }
          if (priv.pwlen < sizeof(priv.pwbuf)) {
            priv.pwbuf[priv.pwlen] = priv.inputbuf;
            ++priv.pwlen;
            priv.displaymarker =
                (priv.displaymarker - 1 + PARANOID_PASSWORD_MIN_CHANGE +
                 rand() % (PARANOID_PASSWORD_LENGTH -
                           2 * PARANOID_PASSWORD_MIN_CHANGE)) %
                    (PARANOID_PASSWORD_LENGTH - 1) +
                1;
          } else {
            Log("Password entered is too long - bailing out");
            done = 1;
            break;
          }
          break;
      }
    }

    // Handle X11 events that queued up.
    while (!done && XPending(display) && (XNextEvent(display, &priv.ev), 1)) {
      if (IsMonitorChangeEvent(display, priv.ev.type)) {
        num_monitors = GetMonitors(display, window, monitors, MAX_MONITORS);
        XClearWindow(display, window);
      }
    }
  }

  // priv contains password related data, so better clear it.
  memset(&priv, 0, sizeof(priv));

  if (!done) {
    Log("Unreachable code - the loop above must set done");
  }
  return status;
}

/*! \brief Perform a single PAM conversation step.
 *
 * \param msg The PAM message.
 * \param resp The PAM response to store the output in.
 * \return The PAM status (PAM_SUCCESS in case of success, or anything else in
 *   case of error).
 */
int converse_one(const struct pam_message *msg, struct pam_response *resp) {
  resp->resp_retcode = 0;  // Unused but should be set to zero.
  switch (msg->msg_style) {
    case PAM_PROMPT_ECHO_OFF:
      return prompt(msg->msg, &resp->resp, 0);
    case PAM_PROMPT_ECHO_ON:
      return prompt(msg->msg, &resp->resp, 1);
    case PAM_ERROR_MSG:
       display_string("Error", msg->msg);
       wait_for_keypress(1);
      return PAM_SUCCESS;
    case PAM_TEXT_INFO:
      display_string("PAM says", msg->msg);
      wait_for_keypress(1);
      return PAM_SUCCESS;
    default:
      return PAM_CONV_ERR;
  }
}

/*! \brief Perform a PAM conversation.
 *
 * \param num_msg The number of conversation steps to execute.
 * \param msg The PAM messages.
 * \param resp The PAM responses to store the output in.
 * \param appdata_ptr Unused.
 * \return The PAM status (PAM_SUCCESS in case of success, or anything else in
 *   case of error).
 */
int converse(int num_msg, const struct pam_message **msg,
             struct pam_response **resp, void *appdata_ptr) {
  (void)appdata_ptr;

  if (conv_error) {
    Log("converse() got called again with %d messages (first: %s) after "
        "having failed before - this is very likely a bug in the PAM "
        "module having made the call. Bailing out",
        num_msg, num_msg <= 0 ? "(none)" : msg[0]->msg);
    exit(1);
  }

  *resp = calloc(num_msg, sizeof(struct pam_response));

  int i;
  for (i = 0; i < num_msg; ++i) {
    int status = converse_one(msg[i], &(*resp)[i]);
    if (status != PAM_SUCCESS) {
      for (i = 0; i < num_msg; ++i) {
        free((*resp)[i].resp);
      }
      free(*resp);
      *resp = NULL;
      conv_error = 1;
      return status;
    }
  }

  // We're returning to PAM, so let's show the processing prompt.
  display_string("", "Processing...");

  return PAM_SUCCESS;
}

/*! \brief Perform a single PAM operation with retrying logic.
 */
int call_pam_with_retries(int (*pam_call)(pam_handle_t *, int),
                          pam_handle_t *pam, int flags) {
  int attempt = 0;
  for (;;) {
    conv_error = 0;

    // We're entering PAM, so let's show a processing prompt.
    display_string("", "Processing...");

    int status = pam_call(pam, flags);
    if (conv_error) {  // Timeout or escape.
      return status;
    }
    switch (status) {
      // Never retry these:
      case PAM_ABORT:             // This is fine.
      case PAM_MAXTRIES:          // D'oh.
      case PAM_NEW_AUTHTOK_REQD:  // hunter2 no longer good enough.
      case PAM_SUCCESS:           // Duh.
        return status;
      default:
        // Let's try again then.
        ++attempt;
        if (attempt >= 3) {
          return status;
        }
        break;
    }
  }
}<commit_msg>Move the processing prompt to the title line.<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include <X11/X.h>                  // for Atom, Success, None, GCBackground
#include <X11/Xlib.h>               // for XDrawString, XTextWidth, XFontStruct
#include <X11/extensions/XKB.h>     // for XkbUseCoreKbd, XkbGroupNamesMask
#include <X11/extensions/XKBstr.h>  // for XkbStateRec, _XkbDesc, _XkbNamesRec
#include <locale.h>                 // for NULL, setlocale, LC_CTYPE
#include <pwd.h>                    // for getpwuid, passwd
#include <security/_pam_types.h>    // for PAM_SUCCESS, pam_strerror, pam_re...
#include <security/pam_appl.h>      // for pam_end, pam_start, pam_acct_mgmt
#include <stdio.h>                  // for fprintf, stderr, LogErrno, NULL
#include <stdlib.h>                 // for mblen, exit, free, calloc, getenv
#include <string.h>                 // for memcpy, strlen, memset
#include <sys/select.h>             // for timeval, select, FD_SET, FD_ZERO
#include <time.h>                   // for time
#include <unistd.h>                 // for gethostname, getuid, read, ssize_t

#ifdef HAVE_XKB_EXT
#include <X11/XKBlib.h>  // for XkbFreeClientMap, XkbGetIndicator...
#endif

#include "../env_settings.h"      // for GetStringSetting
#include "../logging.h"           // for Log, LogErrno
#include "../mlock_page.h"        // for MLOCK_PAGE
#include "../xscreensaver_api.h"  // for ReadWindowID
#include "monitors.h"             // for Monitor, GetMonitors, IsMonitorCh...

//! The blinking interval in microseconds.
#define BLINK_INTERVAL (250 * 1000)

//! The maximum time to wait at a prompt for user input in microseconds.
#define PROMPT_TIMEOUT (5 * 60 * 1000 * 1000)

//! Length of the "paranoid password display".
#define PARANOID_PASSWORD_LENGTH 32

//! Minimum distance the cursor shall move on keypress.
#define PARANOID_PASSWORD_MIN_CHANGE 4

//! Whether password display should hide the length.
int paranoid_password = 1;

//! The X11 display.
Display *display;

//! The X11 window to draw in. Provided from $XSCREENSAVER_WINDOW.
Window window;

//! The X11 graphics context to draw with.
GC gc;

//! The font for the PAM messages.
XFontStruct *font;

//! The Black color (used as background).
unsigned long Black;

//! The White color (used as foreground).
unsigned long White;

//! Set if a conversation error has happened during the last PAM call.
static int conv_error = 0;

//! The cursor character displayed at the end of the masked password input.
static const char cursor[] = "_";

#define MAX_MONITORS 16
static int num_monitors;
static Monitor monitors[MAX_MONITORS];

int have_xkb_ext;

/*! \brief Check which modifiers are active.
 *
 * \return The current modifier mask as a string.
 */
const char *get_indicators() {
#ifdef HAVE_XKB_EXT
  static char buf[128];
  char *p;

  if (!have_xkb_ext) {
    return "";
  }

  XkbDescPtr xkb;
  xkb = XkbGetMap(display, 0, XkbUseCoreKbd);
  if (XkbGetNames(
          display,
          XkbIndicatorNamesMask | XkbGroupNamesMask | XkbSymbolsNameMask,
          xkb) != Success) {
    Log("XkbGetNames failed");
    XkbFreeClientMap(xkb, 0, True);
    return "";
  }
  XkbStateRec state;
  if (XkbGetState(display, XkbUseCoreKbd, &state) != Success) {
    Log("XkbGetState failed");
    XkbFreeClientMap(xkb, 0, True);
    return "";
  }
  unsigned int istate;
  if (XkbGetIndicatorState(display, XkbUseCoreKbd, &istate) != Success) {
    Log("XkbGetIndicatorState failed");
    XkbFreeClientMap(xkb, 0, True);
    return "";
  }

  p = buf;

  const char *word = "Keyboard: ";
  size_t n = strlen(word);
  if (n >= sizeof(buf) - (p - buf)) {
    Log("Not enough space to store intro '%s'", word);
    return "";
  }
  memcpy(p, word, n);
  p += n;

  int have_output = 0;
  Atom layouta = xkb->names->groups[state.group];  // Human-readable.
  if (layouta == None) {
    layouta = xkb->names->symbols;  // Machine-readable fallback.
  }
  if (layouta != None) {
    const char *layout = XGetAtomName(display, layouta);
    n = strlen(layout);
    if (n >= sizeof(buf) - (p - buf)) {
      Log("Not enough space to store layout name '%s'", layout);
      return "";
    }
    memcpy(p, layout, n);
    p += n;
    have_output = 1;
  }

  int i;
  for (i = 0; i < XkbNumIndicators; i++) {
    if (!(istate & (1 << i))) {
      continue;
    }
    Atom namea = xkb->names->indicators[i];
    if (namea == None) {
      continue;
    }
    if (have_output) {
      if (2 >= sizeof(buf) - (p - buf)) {
        Log("Not enough space to store another modifier name");
        break;
      }
      memcpy(p, ", ", 2);
      p += 2;
    }
    const char *name = XGetAtomName(display, namea);
    size_t n = strlen(name);
    if (n >= sizeof(buf) - (p - buf)) {
      Log("Not enough space to store modifier name '%s'", name);
      break;
    }
    memcpy(p, name, n);
    p += n;
    have_output = 1;
  }
  *p = 0;
  return have_output ? buf : "";
#else
  return "";
#endif
}

/*! \brief Display a string in the window.
 *
 * The given title and message will be displayed on all screens. In case caps
 * lock is enabled, the string's case will be inverted.
 *
 * \param title The title of the message.
 * \param str The message itself.
 */
void display_string(const char *title, const char *str) {
  static int region_x;
  static int region_y;
  static int region_w = 0;
  static int region_h = 0;

  int th = font->max_bounds.ascent + font->max_bounds.descent + 4;
  int to = font->max_bounds.ascent + 2;  // Text at to has bbox from 0 to th.

  int len_title = strlen(title);
  int tw_title = XTextWidth(font, title, len_title);

  int len_str = strlen(str);
  int tw_str = XTextWidth(font, str, len_str);

  int tw_cursor = XTextWidth(font, cursor, strlen(cursor));

  const char *indicators = get_indicators();
  int len_indicators = strlen(indicators);
  int tw_indicators = XTextWidth(font, indicators, len_indicators);

  // Compute the region we will be using, relative to cx and cy.
  if (region_w < tw_title + tw_cursor) {
    region_w = tw_title + tw_cursor;
  }
  if (region_w < tw_str + tw_cursor) {
    region_w = tw_str + tw_cursor;
  }
  if (region_w < tw_indicators + tw_cursor) {
    region_w = tw_indicators + tw_cursor;
  }
  region_x = -region_w / 2;
  region_h = 4 * th;
  region_y = -region_h / 2;

  int i;
  for (i = 0; i < num_monitors; ++i) {
    int cx = monitors[i].x + monitors[i].width / 2;
    int cy = monitors[i].y + monitors[i].height / 2;
    int sy = cy + to - th * 2;

    // Clip all following output to the bounds of this monitor.
    XRectangle rect;
    rect.x = monitors[i].x;
    rect.y = monitors[i].y;
    rect.width = monitors[i].width;
    rect.height = monitors[i].height;
    XSetClipRectangles(display, gc, 0, 0, &rect, 1, YXBanded);

    // Clear the region last written to.
    if (region_w != 0 && region_h != 0) {
      XClearArea(display, window, cx + region_x, cy + region_y, region_w,
                 region_h, False);
    }

    XDrawString(display, window, gc, cx - tw_title / 2, sy, title, len_title);

    XDrawString(display, window, gc, cx - tw_str / 2, sy + th * 2, str,
                len_str);
    XDrawString(display, window, gc, cx - tw_indicators / 2, sy + th * 3,
                indicators, len_indicators);

    // Disable clipping again.
    XSetClipMask(display, gc, None);
  }

  // Make the things just drawn appear on the screen as soon as possible.
  XFlush(display);
}

void wait_for_keypress(int seconds) {
  // Sleep for up to 1 second _or_ a key press.
  struct timeval timeout;
  timeout.tv_sec = seconds;
  timeout.tv_usec = 0;
  fd_set set;
  memset(&set, 0, sizeof(set));  // For clang-analyzer.
  FD_ZERO(&set);
  FD_SET(0, &set);
  select(1, &set, NULL, NULL, &timeout);
}

//! The size of the buffer to store the password in. Not NUL terminated.
#define PWBUF_SIZE 256

//! The size of the buffer to use for display, with space for cursor and NUL.
#define DISPLAYBUF_SIZE (PWBUF_SIZE + 2)

/*! \brief Ask a question to the user.
 *
 * \param msg The message.
 * \param response The response will be stored in a newly allocated buffer here.
 *   The caller is supposed to eventually free() it.
 * \param echo If true, the input will be shown; otherwise it will be hidden
 *   (password entry).
 * \return PAM_SUCCESS if successful, anything else otherwise.
 */
int prompt(const char *msg, char **response, int echo) {
  // Ask something. Return strdup'd string.
  struct {
    // The received X11 event.
    XEvent ev;

    // Input buffer. Not NUL-terminated.
    char pwbuf[PWBUF_SIZE];
    // Current input length.
    size_t pwlen;

    // Display buffer. If echo is 0, this will only contain asterisks, a
    // possible cursor, and be NUL-terminated.
    char displaybuf[DISPLAYBUF_SIZE];
    // Display buffer length.
    size_t displaylen;

    // The display marker changes on every input action to a value from 0 to
    // PARANOID_PASSWORD-1. It indicates where to display the "cursor".
    size_t displaymarker;

    // Character read buffer.
    char inputbuf;

    // Temporary position variables that might leak properties about the
    // password and thus are in the private struct too.
    size_t prevpos;
    size_t pos;
    int len;
  } priv;
  int blinks = 0;

  if (!echo && MLOCK_PAGE(&priv, sizeof(priv)) < 0) {
    LogErrno("mlock");
    // We continue anyway, as the user being unable to unlock the screen is
    // worse. But let's alert the user.
    display_string("Error", "Password will not be stored securely.");
    wait_for_keypress(1);
  }

  priv.pwlen = 0;
  priv.displaymarker = rand() % PARANOID_PASSWORD_LENGTH;

  int max_blinks = PROMPT_TIMEOUT / BLINK_INTERVAL;

  // Unfortunately we may have to break out of multiple loops at once here but
  // still do common cleanup work. So we have to track the return value in a
  // variable.
  int status = PAM_CONV_ERR;
  int done = 0;

  while (!done) {
    if (echo) {
      if (priv.pwlen != 0) {
        memcpy(priv.displaybuf, priv.pwbuf, priv.pwlen);
      }
      priv.displaylen = priv.pwlen;
      // Note that priv.pwlen <= sizeof(priv.pwbuf) and thus
      // priv.pwlen + 2 <= sizeof(priv.displaybuf).
      priv.displaybuf[priv.displaylen] = (blinks % 2) ? ' ' : *cursor;
      priv.displaybuf[priv.displaylen + 1] = '\0';
    } else if (paranoid_password) {
      priv.displaylen = PARANOID_PASSWORD_LENGTH;
      memset(priv.displaybuf, '_', priv.displaylen);
      priv.displaybuf[priv.pwlen ? priv.displaymarker : 0] =
          (blinks % 2) ? '|' : '-';
      priv.displaybuf[priv.displaylen] = '\0';
    } else {
      mblen(NULL, 0);
      priv.pos = priv.displaylen = 0;
      while (priv.pos < priv.pwlen) {
        ++priv.displaylen;
        // Note: this won't read past priv.pwlen.
        priv.len = mblen(priv.pwbuf + priv.pos, priv.pwlen - priv.pos);
        if (priv.len <= 0) {
          // This guarantees to "eat" one byte each step. Therefore,
          // priv.displaylen <= priv.pwlen is ensured.
          break;
        }
        priv.pos += priv.len;
      }
      memset(priv.displaybuf, '*', priv.displaylen);
      // Note that priv.pwlen <= sizeof(priv.pwbuf) and thus
      // priv.pwlen + 2 <= sizeof(priv.displaybuf).
      priv.displaybuf[priv.displaylen] = (blinks % 2) ? ' ' : *cursor;
      priv.displaybuf[priv.displaylen + 1] = '\0';
    }
    display_string(msg, priv.displaybuf);

    // Blink the cursor.
    ++blinks;
    if (blinks > max_blinks) {
      done = 1;
      break;
    }

    struct timeval timeout;
    timeout.tv_sec = BLINK_INTERVAL / 1000000;
    timeout.tv_usec = BLINK_INTERVAL % 1000000;

    while (!done) {
      fd_set set;
      memset(&set, 0, sizeof(set));  // For clang-analyzer.
      FD_ZERO(&set);
      FD_SET(0, &set);
      int nfds = select(1, &set, NULL, NULL, &timeout);
      if (nfds < 0) {
        LogErrno("select");
        done = 1;
        break;
      }
      if (nfds == 0) {
        // Blink...
        break;
      }

      // From now on, only do nonblocking selects so we update the screen ASAP.
      timeout.tv_usec = 0;

      // Force the cursor to be in visible state while typing. This also resets
      // the prompt timeout.
      blinks = 0;

      ssize_t nread = read(0, &priv.inputbuf, 1);
      if (nread <= 0) {
        Log("EOF on password input - bailing out");
        done = 1;
        break;
      }
      switch (priv.inputbuf) {
        case '\b':      // Backspace.
        case '\177': {  // Delete (note: i3lock does not handle this one).
          // Backwards skip with multibyte support.
          mblen(NULL, 0);
          priv.pos = priv.prevpos = 0;
          while (priv.pos < priv.pwlen) {
            priv.prevpos = priv.pos;
            // Note: this won't read past priv.pwlen.
            priv.len = mblen(priv.pwbuf + priv.pos, priv.pwlen - priv.pos);
            if (priv.len <= 0) {
              // This guarantees to "eat" one byte each step. Therefore,
              // this cannot loop endlessly.
              break;
            }
            priv.pos += priv.len;
          }
          if (priv.prevpos != priv.pwlen) {
            priv.displaymarker =
                (priv.displaymarker - 1 + PARANOID_PASSWORD_MIN_CHANGE +
                 rand() % (PARANOID_PASSWORD_LENGTH -
                           2 * PARANOID_PASSWORD_MIN_CHANGE)) %
                    (PARANOID_PASSWORD_LENGTH - 1) +
                1;
          }
          priv.pwlen = priv.prevpos;
          break;
        }
        case '\001':  // Ctrl-A.
          // Clearing input line on just Ctrl-A is odd - but commonly
          // requested. In most toolkits, Ctrl-A does not immediately erase but
          // almost every keypress other than arrow keys will erase afterwards.
          priv.pwlen = 0;
          break;
        case '\025':  // Ctrl-U.
          // Delete the entire input line.
          // i3lock: supports Ctrl-U but not Ctrl-A.
          // xscreensaver: supports Ctrl-U and Ctrl-X but not Ctrl-A.
          priv.pwlen = 0;
          break;
        case 0:       // Shouldn't happen.
        case '\033':  // Escape.
          done = 1;
          break;
        case '\r':  // Return.
        case '\n':  // Return.
          *response = malloc(priv.pwlen + 1);
          if (!echo && MLOCK_PAGE(*response, priv.pwlen + 1) < 0) {
            LogErrno("mlock");
            // We continue anyway, as the user being unable to unlock the screen
            // is worse. But let's alert the user of this.
            display_string("Error", "Password has not been stored securely.");
            wait_for_keypress(1);
          }
          if (priv.pwlen != 0) {
            memcpy(*response, priv.pwbuf, priv.pwlen);
          }
          (*response)[priv.pwlen] = 0;
          status = PAM_SUCCESS;
          done = 1;
          break;
        default:
          if (priv.inputbuf >= '\000' && priv.inputbuf <= '\037') {
            // Other control character. We ignore them (and specifically do not
            // update the cursor on them) to "discourage" their use in
            // passwords, as most login screens do not support them anyway.
            break;
          }
          if (priv.pwlen < sizeof(priv.pwbuf)) {
            priv.pwbuf[priv.pwlen] = priv.inputbuf;
            ++priv.pwlen;
            priv.displaymarker =
                (priv.displaymarker - 1 + PARANOID_PASSWORD_MIN_CHANGE +
                 rand() % (PARANOID_PASSWORD_LENGTH -
                           2 * PARANOID_PASSWORD_MIN_CHANGE)) %
                    (PARANOID_PASSWORD_LENGTH - 1) +
                1;
          } else {
            Log("Password entered is too long - bailing out");
            done = 1;
            break;
          }
          break;
      }
    }

    // Handle X11 events that queued up.
    while (!done && XPending(display) && (XNextEvent(display, &priv.ev), 1)) {
      if (IsMonitorChangeEvent(display, priv.ev.type)) {
        num_monitors = GetMonitors(display, window, monitors, MAX_MONITORS);
        XClearWindow(display, window);
      }
    }
  }

  // priv contains password related data, so better clear it.
  memset(&priv, 0, sizeof(priv));

  if (!done) {
    Log("Unreachable code - the loop above must set done");
  }
  return status;
}

/*! \brief Perform a single PAM conversation step.
 *
 * \param msg The PAM message.
 * \param resp The PAM response to store the output in.
 * \return The PAM status (PAM_SUCCESS in case of success, or anything else in
 *   case of error).
 */
int converse_one(const struct pam_message *msg, struct pam_response *resp) {
  resp->resp_retcode = 0;  // Unused but should be set to zero.
  switch (msg->msg_style) {
    case PAM_PROMPT_ECHO_OFF:
      return prompt(msg->msg, &resp->resp, 0);
    case PAM_PROMPT_ECHO_ON:
      return prompt(msg->msg, &resp->resp, 1);
    case PAM_ERROR_MSG:
       display_string("Error", msg->msg);
       wait_for_keypress(1);
      return PAM_SUCCESS;
    case PAM_TEXT_INFO:
      display_string("PAM says", msg->msg);
      wait_for_keypress(1);
      return PAM_SUCCESS;
    default:
      return PAM_CONV_ERR;
  }
}

/*! \brief Perform a PAM conversation.
 *
 * \param num_msg The number of conversation steps to execute.
 * \param msg The PAM messages.
 * \param resp The PAM responses to store the output in.
 * \param appdata_ptr Unused.
 * \return The PAM status (PAM_SUCCESS in case of success, or anything else in
 *   case of error).
 */
int converse(int num_msg, const struct pam_message **msg,
             struct pam_response **resp, void *appdata_ptr) {
  (void)appdata_ptr;

  if (conv_error) {
    Log("converse() got called again with %d messages (first: %s) after "
        "having failed before - this is very likely a bug in the PAM "
        "module having made the call. Bailing out",
        num_msg, num_msg <= 0 ? "(none)" : msg[0]->msg);
    exit(1);
  }

  *resp = calloc(num_msg, sizeof(struct pam_response));

  int i;
  for (i = 0; i < num_msg; ++i) {
    int status = converse_one(msg[i], &(*resp)[i]);
    if (status != PAM_SUCCESS) {
      for (i = 0; i < num_msg; ++i) {
        free((*resp)[i].resp);
      }
      free(*resp);
      *resp = NULL;
      conv_error = 1;
      return status;
    }
  }

  // We're returning to PAM, so let's show the processing prompt.
  display_string("Processing...", "");

  return PAM_SUCCESS;
}

/*! \brief Perform a single PAM operation with retrying logic.
 */
int call_pam_with_retries(int (*pam_call)(pam_handle_t *, int),
                          pam_handle_t *pam, int flags) {
  int attempt = 0;
  for (;;) {
    conv_error = 0;

    // We're entering PAM, so let's show a processing prompt.
    display_string("Processing...", "");

    int status = pam_call(pam, flags);
    if (conv_error) {  // Timeout or escape.
      return status;
    }
    switch (status) {
      // Never retry these:
      case PAM_ABORT:             // This is fine.
      case PAM_MAXTRIES:          // D'oh.
      case PAM_NEW_AUTHTOK_REQD:  // hunter2 no longer good enough.
      case PAM_SUCCESS:           // Duh.
        return status;
      default:
        // Let's try again then.
        ++attempt;
        if (attempt >= 3) {
          return status;
        }
        break;
    }
  }
}
{'fraction_non_alphanumeric': 0.11820659030125649, 'fraction_numerical': 0.010596962204168139, 'mean_word_length': 2.257046593803928, 'pattern_counts': {'":': 0, '<': 83, '<?xml version=': 0, '>': 83, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '476123', 'n_tokens_mistral': 13294, 'n_tokens_neox': 12644, 'n_words': 4023}