 by
 *   Digest Auth, so transition to Digest Auth is simple change in lighttpd.conf
 * future: might move to mod_auth.c and have only mod_auth.so depend on -lcrypt
 * references:
 *   https://en.wikipedia.org/wiki/Crypt_(C)
 *   Modular Crypt Format (MCF)
 *   https://passlib.readthedocs.io/en/stable/modular_crypt_format.html
 *   PHC string format
 *   https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
 * Note: (struct crypt_data) is large and might not fit on the stack
 *   On some systems it is 32k, on others 128k or more.
 *   Therefore, since lighttpd is single-threaded, simply use crypt(),
 *     which is not thread-safe but fine for single-threaded lighttpd.
 *   If crypt_r() is preferred, then safest method is to allocate from heap
 *   rather than using (struct crypt_data) on stack.
 */
#if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT)
static int
mod_authn_crypt_cmp (const char *reqpw, const char *userpw, unsigned long userpwlen)
{
 #if 1

    char *crypted = crypt(reqpw, userpw);
    size_t crypwlen = (NULL!= crypted)? strlen(crypted) : 0;
    int rc = (crypwlen == userpwlen)? memcmp(crypted, userpw, crypwlen) : -1;
    if (crypwlen) ck_memzero(crypted, crypwlen);
    return rc;

 #else

  #if defined(HAVE_CRYPT_R)
   #if 1 /* (must free() before returning if allocated here) */
    struct crypt_data *crypt_tmp_data = malloc(sizeof(struct crypt_data));
    force_assert(crypt_tmp_data);
   #else /* safe if sizeof(struct crypt_data) <= 32768 */
    struct crypt_data crypt_tmp_data_stack;
    struct crypt_data *crypt_tmp_data = &crypt_tmp_data_stack;
   #endif
   #ifdef _AIX
    memset(&crypt_tmp_data_stack, 0, sizeof(struct crypt_data));
   #else
    crypt_tmp_data_stack.initialized = 0;
   #endif
  #endif

  #if defined(HAVE_CRYPT_R)
    char *crypted = crypt_r(reqpw, userpw, crypt_tmp_data);
  #else
    char *crypted = crypt(reqpw, userpw);
  #endif
    size_t crypwlen = (NULL!= crypted)? strlen(crypted) : 0;
    int rc = (crypwlen == userpwlen)? memcmp(crypted, userpw, crypwlen) : -1;

    ck_memzero(crypted, crypwlen);
  #if defined(HAVE_CRYPT_R)
   #if 1 /* (must free() if allocated above) */
    free(crypt_tmp_data);
   #else /* safe if sizeof(struct crypt_data) <= 32768 */
   #endif
  #endif
    return rc;

 #endif
}
#endif


static int
mod_authn_dbi_password_cmp (const char *userpw, unsigned long userpwlen, http_auth_info_t * const ai, const char *reqpw)
{
  #if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT)
    if (userpwlen >= 3 && userpw[0] == '$')
        return mod_authn_crypt_cmp(reqpw, userpw, userpwlen);
  #endif

    const struct const_iovec iov[] = {
      { ai->username, ai->ulen }
    ,{ ":", 1 }
    ,{ ai->realm, ai->rlen }
    ,{ ":", 1 }
    ,{ reqpw, strlen(reqpw) }
    };

    unsigned char HA1[MD_DIGEST_LENGTH_MAX];
    unsigned char pwbin[MD_DIGEST_LENGTH_MAX];

    if (32 == userpwlen)
        MD5_iov(HA1, iov, sizeof(iov)/sizeof(*iov));
  #ifdef USE_LIB_CRYPTO
    else if (64 == userpwlen)
        SHA256_iov(HA1, iov, sizeof(iov)/sizeof(*iov));
  #endif
    else
        return -1;

    /*(compare 32-byte binary digest instead of converting to hex strings
     * in order to then have to do case-insensitive hex str comparison)*/
    return (0 == li_hex2bin(pwbin, sizeof(pwbin), userpw, userpwlen))
     ? ck_memeq_const_time_fixed_len(HA1, pwbin, userpwlen>>1)? 0 : 1
      : -1;
}


static buffer *
mod_authn_dbi_query_build (buffer * const sqlquery, dbi_config * const dbconf, http_auth_info_t * const ai)
{
    char buf[1024];
    buffer_clear(sqlquery);
    int qcount = 0;
    for (char *b = dbconf->sqlquery->ptr, *d; *b; b = d+1) {
        if (NULL!= (d = strchr(b, '?'))) {
            /* Substitute for up to three question marks (?)
             *   substitute username for first question mark
             *   substitute realm for second question mark
             *   substitute digest algorithm for third question mark */
            const char *v;
            switch (++qcount) {
              case 1:
                if (ai->ulen < sizeof(buf)) {
                    memcpy(buf, ai->username, ai->ulen);
                    buf[ai->ulen] = '\0';
                    v = buf;
                }
                else
                    return NULL;
                break;
              case 2:
                if (ai->rlen < sizeof(buf)) {
                    memcpy(buf, ai->realm, ai->rlen);
                    buf[ai->rlen] = '\0';
                    v = buf;
                }
                else
                    return NULL;
                break;
              case 3:
                if (ai->dalgo & HTTP_AUTH_DIGEST_SHA256)
                    v = "SHA-256";
                else if (ai->dalgo & HTTP_AUTH_DIGEST_MD5)
                    v = "MD5";
                else if (ai->dalgo == HTTP_AUTH_DIGEST_NONE)
                    v = "NONE";
                else
                    return NULL;
                break;
              default:
                return NULL;
            }
            /* escape the value */
            char *esc = NULL;
            size_t elen =
              dbi_conn_escape_string_copy(dbconf->dbconn, v, &esc);
            if (0 == elen) return NULL; /*('esc' must not be freed if error)*/
            buffer_append_string_len(sqlquery, b, (size_t)(d - b));
            buffer_append_string_len(sqlquery, esc, elen);
            free(esc);
        }
        else {
            d = dbconf->sqlquery->ptr + buffer_clen(dbconf->sqlquery);
            buffer_append_string_len(sqlquery, b, (size_t)(d - b));
            break;
        }
    }

    return sqlquery;
}


static handler_t
mod_authn_dbi_query (request_st * const r, void *p_d, http_auth_info_t * const ai, const char * const pw)
{
    plugin_data * const p = (plugin_data *)p_d;
    mod_authn_dbi_patch_config(r, p);
    if (NULL == p->conf.vdata) return HANDLER_ERROR; /*(should not happen)*/
    dbi_config * const dbconf = (dbi_config *)p->conf.vdata;

    buffer * const sqlquery = mod_authn_dbi_query_build(r->tmp_buf, dbconf, ai);
    if (NULL == sqlquery)
        return HANDLER_ERROR;

    /* reset our reconnect-attempt counter, this is a new query. */
    dbconf->reconnect_count = 0;

    dbi_result result;