Parcourir la source

Digest Auth: part one

Evgeny Grin (Karlson2k) il y a 1 an
Parent
commit
6a1a29c795

+ 10 - 10
configure.ac

@@ -5635,18 +5635,18 @@ AS_CASE([${enable_md5}],
 )
 AS_IF([test "x${enable_md5}" = "xbuiltin" || test "x${enable_md5}" = "xtlslib" ],
   [
-    AC_DEFINE([[MHD_MD5_SUPPORT]],[[1]],
+    AC_DEFINE([[MHD_SUPPORT_MD5]],[[1]],
       [Define to 1 if libmicrohttpd is compiled with MD5 hashing support.])
   ]
 )
 AS_IF([test "x${enable_md5}" = "xtlslib" ],
   [
-    AC_DEFINE([[MHD_MD5_TLSLIB]],[[1]],
+    AC_DEFINE([[MHD_MD5_EXTR]],[[1]],
       [Define to 1 if libmicrohttpd is compiled with MD5 hashing by TLS library.])
   ]
 )
-AM_CONDITIONAL([ENABLE_MD5], [[test "x${enable_md5}" = "xbuiltin" || test "x${enable_md5}" = "xtlslib" ]])
-AM_CONDITIONAL([ENABLE_MD5_EXT], [[test "x${enable_md5}" = "xtlslib" ]])
+AM_CONDITIONAL([MHD_SUPPORT_MD5], [[test "x${enable_md5}" = "xbuiltin" || test "x${enable_md5}" = "xtlslib" ]])
+AM_CONDITIONAL([MHD_MD5_EXTR], [[test "x${enable_md5}" = "xtlslib" ]])
 AC_MSG_RESULT([[${enable_md5_MSG}]])
 
 # optional: SHA-256 support for Digest Auth. Enabled by default.
@@ -5757,18 +5757,18 @@ AS_CASE([${enable_sha256}],
 )
 AS_IF([test "x${enable_sha256}" = "xbuiltin" || test "x${enable_sha256}" = "xtlslib" ],
   [
-    AC_DEFINE([[MHD_SHA256_SUPPORT]],[[1]],
+    AC_DEFINE([[MHD_SUPPORT_SHA256]],[[1]],
       [Define to 1 if libmicrohttpd is compiled with SHA-256 hashing support.])
   ]
 )
 AS_IF([test "x${enable_sha256}" = "xtlslib" ],
   [
-    AC_DEFINE([[MHD_SHA256_TLSLIB]],[[1]],
+    AC_DEFINE([[MHD_SHA256_EXTR]],[[1]],
       [Define to 1 if libmicrohttpd is compiled with SHA-256 hashing by TLS library.])
   ]
 )
-AM_CONDITIONAL([ENABLE_SHA256], [[test "x${enable_sha256}" = "xbuiltin" || test "x${enable_sha256}" = "xtlslib" ]])
-AM_CONDITIONAL([ENABLE_SHA256_EXT], [[test "x${enable_sha256}" = "xtlslib" ]])
+AM_CONDITIONAL([MHD_SUPPORT_SHA256], [[test "x${enable_sha256}" = "xbuiltin" || test "x${enable_sha256}" = "xtlslib" ]])
+AM_CONDITIONAL([MHD_SHA256_EXTR], [[test "x${enable_sha256}" = "xtlslib" ]])
 AC_MSG_RESULT([[${enable_sha256_MSG}]])
 
 # optional: SHA-512/256 support for Digest Auth. Enabled by default.
@@ -5797,11 +5797,11 @@ AS_CASE([${enable_sha512_256}],
 )
 AS_VAR_IF([[enable_sha512_256]],[["yes"]],
   [
-   AC_DEFINE([[MHD_SHA512_256_SUPPORT]],[[1]],
+   AC_DEFINE([[MHD_SUPPORT_SHA512_256]],[[1]],
      [Define to 1 if libmicrohttpd is compiled with SHA-512/256 hashing support.])
   ]
 )
-AM_CONDITIONAL([ENABLE_SHA512_256], [[test "x${enable_sha512_256}" = "xyes"]])
+AM_CONDITIONAL([MHD_SUPPORT_SHA512_256], [[test "x${enable_sha512_256}" = "xyes"]])
 AC_MSG_RESULT([[${enable_sha512_256_MSG}]])
 
 AS_IF([test "x$enable_digest_auth" != "xno"],

+ 291 - 35
src/include/microhttpd2.h

@@ -1621,6 +1621,16 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode
    * cannot be provided due to some error or other reasons.
    */
   MHD_SC_INFO_GET_TYPE_UNAVAILALBE = 60204
+  ,
+  /**
+   * The type of the Digest Auth algorithm is unknown or not supported.
+   */
+  MHD_SC_AUTH_DIGEST_ALGO_NOT_SUPPORTED = 60240
+  ,
+  /**
+   * The the Digest Auth QOP value is unknown or not supported.
+   */
+  MHD_SC_AUTH_DIGEST_QOP_NOT_SUPPORTED = 60241
 };
 
 /**
@@ -7907,20 +7917,32 @@ MHD_FN_PAR_CSTR_ (4);
 
 
 /**
- * Create an action to request authentication from the client
+ * Add Digest Authentication "challenge" to the response.
+ *
+ * The response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code.
  *
- * If @a mqop allows both RFC 2069 (MHD_DIGEST_AUTH_QOP_NONE) and QOP with
- * value, then response is formed like if MHD_DIGEST_AUTH_QOP_NONE bit was
- * not set, because such response should be backward-compatible with RFC 2069.
+ * If @a mqop allows both RFC 2069 (#MHD_DIGEST_AUTH_QOP_NONE) and other QOP
+ * values, then the "challenge" is formed like if MHD_DIGEST_AUTH_QOP_NONE bit
+ * was not set, because such "challenge" should be backward-compatible with
+ * RFC 2069.
  *
  * If @a mqop allows only MHD_DIGEST_AUTH_MULT_QOP_NONE, then the response is
  * formed in strict accordance with RFC 2069 (no 'qop', no 'userhash', no
  * 'charset'). For better compatibility with clients, it is recommended (but
  * not required) to set @a domain to NULL in this mode.
  *
- * At most one action can be created for any request.
+ * New nonces are generated each time when the resulting response is used.
  *
- * @param request the request
+ * See RFC 7616, section 3.3 for details.
+ *
+ * @param response the response to update; should contain the "access denied"
+ *                 body;
+ *                 note: this function sets the "WWW Authenticate" header and
+ *                 the caller should not set this header;
+ *                 the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status
+ *                 code;
+ *                 the NULL is tolerated (the result is
+ *                 #MHD_SC_RESP_POINTER_NULL)
  * @param realm the realm presented to the client
  * @param opaque the string for opaque value, can be NULL, but NULL is
  *               not recommended for better compatibility with clients;
@@ -7935,24 +7957,17 @@ MHD_FN_PAR_CSTR_ (4);
  *               credentials could be used for any URI on the same host);
  *               this list provides information for the client only and does
  *               not actually restrict anything on the server side
- * @param response the reply to send; should contain the "access denied"
- *                 body;
- *                 note: this function sets the "WWW Authenticate" header and
- *                 the caller should not set this header;
- *                 the response must have #MHD_HTTP_STATUS_FORBIDDEN status
- *                 code, must not have #MHD_R_O_REUSABLE enabled;
- *                 the NULL is tolerated (the result is NULL)
- * @param signal_stale if set to #MHD_YES then indication of stale nonce used in
- *                     the client's request is signalled by adding 'stale=true'
- *                     to the authentication header, this instructs the client
- *                     to retry immediately with the new nonce and the same
- *                     credentials, without asking user for the new password
+ * @param indicate_stale if set to #MHD_YES then indication of stale nonce used
+ *                       in the client's request is indicated by adding
+ *                       'stale=true' to the authentication header, this
+ *                       instructs the client to retry immediately with the new
+ *                       nonce and the same credentials, without asking user
+ *                       for the new password
  * @param mqop the QOP to use
  * @param algo digest algorithm to use; if several algorithms are allowed
- *               then MD5 is preferred (currently, may be changed in next
- *               versions)
- * @param userhash_support if set to non-zero value (#MHD_YES) then support of
- *                         userhash is indicated, allowing client to provide
+ *             then one challenge for each allowed algorithm is added
+ * @param userhash_support if set to #MHD_YES then support of userhash is
+ *                         indicated, allowing client to provide
  *                         hash("username:realm") instead of the username in
  *                         clear text;
  *                         note that clients are allowed to provide the username
@@ -7964,24 +7979,265 @@ MHD_FN_PAR_CSTR_ (4);
  * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is
  *                    added, indicating for the client that UTF-8 encoding for
  *                    the username is preferred
- * @return pointer to the action on success,
- *         NULL on failure
+ * @return #MHD_SC_OK if succeed,
+ *         #MHD_SC_TOO_LATE if the response has been already "frozen" (used to
+ *         create an action),
+ *         #MHD_SC_RESP_HEADERS_CONFLICT if Digest Authentication "challenge"
+ *         has been added already,
+ *         #MHD_SC_RESP_POINTER_NULL if @a response is NULL,
+ *         #MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE is response status code is wrong,
+ *         #MHD_SC_RESP_HEADER_VALUE_INVALID if @a realm, @a opaque or @a domain
+ *         have wrong characters or zero length (for @a realm),
+ *         #MHD_SC_RESPONSE_HEADER_MEM_ALLOC_FAILED if memory allocation failed,
+ *         or other error code if failed
  * @ingroup authentication
  */
-MHD_EXTERN_ const struct MHD_Action *
-MHD_action_digest_auth_required_response (
-  struct MHD_Request *request,
-  const char *realm,
-  const char *opaque,
-  const char *domain,
-  struct MHD_Response *response,
-  enum MHD_Bool signal_stale,
+MHD_EXTERN_ enum MHD_StatusCode
+MHD_response_add_auth_digest_challenge (
+  struct MHD_Response *MHD_RESTRICT response,
+  const char *MHD_RESTRICT realm,
+  const char *MHD_RESTRICT opaque,
+  const char *MHD_RESTRICT domain,
+  enum MHD_Bool indicate_stale,
   enum MHD_DigestAuthMultiQOP mqop,
-  enum MHD_DigestAuthMultiAlgo algo,
+  enum MHD_DigestAuthMultiAlgo malgo,
   enum MHD_Bool userhash_support,
   enum MHD_Bool prefer_utf8)
-MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2)
-MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_NONNULL_ (5);
+MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2)
+MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4);
+
+
+#ifndef MHD_NO_STATIC_INLINE
+
+/**
+ * Create action to reply with Digest Authentication "challenge".
+ *
+ * The @a response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code.
+ *
+ * See RFC 7616, section 3.3 for details.
+ *
+ * @param request the request to create the action for
+ * @param realm the realm presented to the client
+ * @param opaque the string for opaque value, can be NULL, but NULL is
+ *               not recommended for better compatibility with clients;
+ *               the recommended format is hex or Base64 encoded string
+ * @param domain the optional space-separated list of URIs for which the
+ *               same authorisation could be used, URIs can be in form
+ *               "path-absolute" (the path for the same host with initial slash)
+ *               or in form "absolute-URI" (the full path with protocol), in
+ *               any case client may assume that URI is in the same "protection
+ *               space" if it starts with any of values specified here;
+ *               could be NULL (clients typically assume that the same
+ *               credentials could be used for any URI on the same host);
+ *               this list provides information for the client only and does
+ *               not actually restrict anything on the server side
+ * @param indicate_stale if set to #MHD_YES then indication of stale nonce used
+ *                       in the client's request is indicated by adding
+ *                       'stale=true' to the authentication header, this
+ *                       instructs the client to retry immediately with the new
+ *                       nonce and the same credentials, without asking user
+ *                       for the new password
+ * @param mqop the QOP to use
+ * @param algo digest algorithm to use; if several algorithms are allowed
+ *             then one challenge for each allowed algorithm is added
+ * @param userhash_support if set to #MHD_YES then support of userhash is
+ *                         indicated, allowing client to provide
+ *                         hash("username:realm") instead of the username in
+ *                         clear text;
+ *                         note that clients are allowed to provide the username
+ *                         in cleartext even if this parameter set to non-zero;
+ *                         when userhash is used, application must be ready to
+ *                         identify users by provided userhash value instead of
+ *                         username; see #MHD_digest_auth_calc_userhash() and
+ *                         #MHD_digest_auth_calc_userhash_hex()
+ * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is
+ *                    added, indicating for the client that UTF-8 encoding for
+ *                    the username is preferred
+ * @param response the response to update; should contain the "access denied"
+ *                 body;
+ *                 note: this function sets the "WWW Authenticate" header and
+ *                 the caller should not set this header;
+ *                 the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status
+ *                 code;
+ *                 the NULL is tolerated (the result is
+ *                 #MHD_SC_RESP_POINTER_NULL)
+ * @param abort_if_failed if set to #MHD_NO the response will be used even if
+ *                        failed to add Basic Authentication "challenge",
+ *                        if not set to #MHD_NO the request will be aborted
+ *                        if the "challenge" could not be added.
+ * @return pointer to the action, the action must be consumed
+ *         otherwise response object may leak;
+ *         NULL if failed or if any action has been already created for
+ *         the @a request;
+ *         when failed the response object is consumed and need not
+ *         to be "destroyed"
+ * @ingroup authentication
+ */
+MHD_STATIC_INLINE_
+MHD_FN_PAR_NONNULL_ (1)
+MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2)
+const struct MHD_Action *
+MHD_action_digest_auth_challenge (struct MHD_Request *MHD_RESTRICT request,
+                                  const char *MHD_RESTRICT realm,
+                                  const char *MHD_RESTRICT opaque,
+                                  const char *MHD_RESTRICT domain,
+                                  enum MHD_Bool indicate_stale,
+                                  enum MHD_DigestAuthMultiQOP mqop,
+                                  enum MHD_DigestAuthMultiAlgo malgo,
+                                  enum MHD_Bool userhash_support,
+                                  enum MHD_Bool prefer_utf8,
+                                  struct MHD_Response *MHD_RESTRICT response,
+                                  enum MHD_Bool abort_if_failed)
+{
+  if ((MHD_SC_OK !=
+       MHD_response_add_auth_digest_challenge (response, realm, opaque, domain,
+                                               indicate_stale, mqop, malgo,
+                                               userhash_support, prefer_utf8))
+      && (MHD_NO != abort_if_failed))
+  {
+    MHD_response_destroy (response);
+    return MHD_action_abort_request (request);
+  }
+  return MHD_action_from_response (request, response);
+}
+
+
+MHD_STATIC_INLINE_END_
+
+/**
+ * Create action to reply with Digest Authentication "challenge".
+ *
+ * The @a response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code.
+ *
+ * If the @a response object cannot be extended with the "challenge",
+ * the @a response is used to reply without the "challenge".
+ *
+ * @param request the request to create the action for
+ * @param realm the realm presented to the client
+ * @param opaque the string for opaque value, can be NULL, but NULL is
+ *               not recommended for better compatibility with clients;
+ *               the recommended format is hex or Base64 encoded string
+ * @param domain the optional space-separated list of URIs for which the
+ *               same authorisation could be used, URIs can be in form
+ *               "path-absolute" (the path for the same host with initial slash)
+ *               or in form "absolute-URI" (the full path with protocol), in
+ *               any case client may assume that URI is in the same "protection
+ *               space" if it starts with any of values specified here;
+ *               could be NULL (clients typically assume that the same
+ *               credentials could be used for any URI on the same host);
+ *               this list provides information for the client only and does
+ *               not actually restrict anything on the server side
+ * @param indicate_stale if set to #MHD_YES then indication of stale nonce used
+ *                       in the client's request is indicated by adding
+ *                       'stale=true' to the authentication header, this
+ *                       instructs the client to retry immediately with the new
+ *                       nonce and the same credentials, without asking user
+ *                       for the new password
+ * @param mqop the QOP to use
+ * @param algo digest algorithm to use; if several algorithms are allowed
+ *             then one challenge for each allowed algorithm is added
+ * @param userhash_support if set to #MHD_YES then support of userhash is
+ *                         indicated, allowing client to provide
+ *                         hash("username:realm") instead of the username in
+ *                         clear text;
+ *                         note that clients are allowed to provide the username
+ *                         in cleartext even if this parameter set to non-zero;
+ *                         when userhash is used, application must be ready to
+ *                         identify users by provided userhash value instead of
+ *                         username; see #MHD_digest_auth_calc_userhash() and
+ *                         #MHD_digest_auth_calc_userhash_hex()
+ * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is
+ *                    added, indicating for the client that UTF-8 encoding for
+ *                    the username is preferred
+ * @param response the response to update; should contain the "access denied"
+ *                 body;
+ *                 note: this function sets the "WWW Authenticate" header and
+ *                 the caller should not set this header;
+ *                 the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status
+ *                 code;
+ *                 the NULL is tolerated (the result is
+ *                 #MHD_SC_RESP_POINTER_NULL)
+ * @return pointer to the action, the action must be consumed
+ *         otherwise response object may leak;
+ *         NULL if failed or if any action has been already created for
+ *         the @a request;
+ *         when failed the response object is consumed and need not
+ *         to be "destroyed"
+ * @ingroup authentication
+ */
+#define MHD_action_digest_auth_challenge_p(rq,rlm,opq,dmn,stl,mqop,malgo, \
+                                           uh,utf,resp) \
+        MHD_action_digest_auth_challenge ((rq),(rlm),(opq),(dmn),(stl),(mqop), \
+                                          (malgo),(uh),(utf),(resp),MHD_NO)
+
+
+/**
+ * Create action to reply with Digest Authentication "challenge".
+ *
+ * The @a response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code.
+ *
+ * If the @a response object cannot be extended with the "challenge",
+ * the @a response is aborted.
+ *
+ * @param request the request to create the action for
+ * @param realm the realm presented to the client
+ * @param opaque the string for opaque value, can be NULL, but NULL is
+ *               not recommended for better compatibility with clients;
+ *               the recommended format is hex or Base64 encoded string
+ * @param domain the optional space-separated list of URIs for which the
+ *               same authorisation could be used, URIs can be in form
+ *               "path-absolute" (the path for the same host with initial slash)
+ *               or in form "absolute-URI" (the full path with protocol), in
+ *               any case client may assume that URI is in the same "protection
+ *               space" if it starts with any of values specified here;
+ *               could be NULL (clients typically assume that the same
+ *               credentials could be used for any URI on the same host);
+ *               this list provides information for the client only and does
+ *               not actually restrict anything on the server side
+ * @param indicate_stale if set to #MHD_YES then indication of stale nonce used
+ *                       in the client's request is indicated by adding
+ *                       'stale=true' to the authentication header, this
+ *                       instructs the client to retry immediately with the new
+ *                       nonce and the same credentials, without asking user
+ *                       for the new password
+ * @param mqop the QOP to use
+ * @param algo digest algorithm to use; if several algorithms are allowed
+ *             then one challenge for each allowed algorithm is added
+ * @param userhash_support if set to #MHD_YES then support of userhash is
+ *                         indicated, allowing client to provide
+ *                         hash("username:realm") instead of the username in
+ *                         clear text;
+ *                         note that clients are allowed to provide the username
+ *                         in cleartext even if this parameter set to non-zero;
+ *                         when userhash is used, application must be ready to
+ *                         identify users by provided userhash value instead of
+ *                         username; see #MHD_digest_auth_calc_userhash() and
+ *                         #MHD_digest_auth_calc_userhash_hex()
+ * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is
+ *                    added, indicating for the client that UTF-8 encoding for
+ *                    the username is preferred
+ * @param response the response to update; should contain the "access denied"
+ *                 body;
+ *                 note: this function sets the "WWW Authenticate" header and
+ *                 the caller should not set this header;
+ *                 the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status
+ *                 code;
+ *                 the NULL is tolerated (the result is
+ *                 #MHD_SC_RESP_POINTER_NULL)
+ * @return pointer to the action, the action must be consumed
+ *         otherwise response object may leak;
+ *         NULL if failed or if any action has been already created for
+ *         the @a request;
+ *         when failed the response object is consumed and need not
+ *         to be "destroyed"
+ * @ingroup authentication
+ */
+#define MHD_action_digest_auth_challenge_a(rq,rlm,opq,dmn,stl,mqop,malgo, \
+                                           uh,utf,resp) \
+        MHD_action_digest_auth_challenge ((rq),(rlm),(opq),(dmn),(stl),(mqop), \
+                                          (malgo),(uh),(utf),(resp),MHD_YES)
+
+#endif /* ! MHD_NO_STATIC_INLINE */
 
 
 /**

+ 281 - 35
src/include/microhttpd2_main.h.in

@@ -3325,20 +3325,32 @@ MHD_FN_PAR_CSTR_ (4);
 
 
 /**
- * Create an action to request authentication from the client
+ * Add Digest Authentication "challenge" to the response.
  *
- * If @a mqop allows both RFC 2069 (MHD_DIGEST_AUTH_QOP_NONE) and QOP with
- * value, then response is formed like if MHD_DIGEST_AUTH_QOP_NONE bit was
- * not set, because such response should be backward-compatible with RFC 2069.
+ * The response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code.
+ *
+ * If @a mqop allows both RFC 2069 (#MHD_DIGEST_AUTH_QOP_NONE) and other QOP
+ * values, then the "challenge" is formed like if MHD_DIGEST_AUTH_QOP_NONE bit
+ * was not set, because such "challenge" should be backward-compatible with
+ * RFC 2069.
  *
  * If @a mqop allows only MHD_DIGEST_AUTH_MULT_QOP_NONE, then the response is
  * formed in strict accordance with RFC 2069 (no 'qop', no 'userhash', no
  * 'charset'). For better compatibility with clients, it is recommended (but
  * not required) to set @a domain to NULL in this mode.
  *
- * At most one action can be created for any request.
+ * New nonces are generated each time when the resulting response is used.
  *
- * @param request the request
+ * See RFC 7616, section 3.3 for details.
+ *
+ * @param response the response to update; should contain the "access denied"
+ *                 body;
+ *                 note: this function sets the "WWW Authenticate" header and
+ *                 the caller should not set this header;
+ *                 the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status
+ *                 code;
+ *                 the NULL is tolerated (the result is
+ *                 #MHD_SC_RESP_POINTER_NULL)
  * @param realm the realm presented to the client
  * @param opaque the string for opaque value, can be NULL, but NULL is
  *               not recommended for better compatibility with clients;
@@ -3353,24 +3365,17 @@ MHD_FN_PAR_CSTR_ (4);
  *               credentials could be used for any URI on the same host);
  *               this list provides information for the client only and does
  *               not actually restrict anything on the server side
- * @param response the reply to send; should contain the "access denied"
- *                 body;
- *                 note: this function sets the "WWW Authenticate" header and
- *                 the caller should not set this header;
- *                 the response must have #MHD_HTTP_STATUS_FORBIDDEN status
- *                 code, must not have #MHD_R_O_REUSABLE enabled;
- *                 the NULL is tolerated (the result is NULL)
- * @param signal_stale if set to #MHD_YES then indication of stale nonce used in
- *                     the client's request is signalled by adding 'stale=true'
- *                     to the authentication header, this instructs the client
- *                     to retry immediately with the new nonce and the same
- *                     credentials, without asking user for the new password
+ * @param indicate_stale if set to #MHD_YES then indication of stale nonce used
+ *                       in the client's request is indicated by adding
+ *                       'stale=true' to the authentication header, this
+ *                       instructs the client to retry immediately with the new
+ *                       nonce and the same credentials, without asking user
+ *                       for the new password
  * @param mqop the QOP to use
  * @param algo digest algorithm to use; if several algorithms are allowed
- *               then MD5 is preferred (currently, may be changed in next
- *               versions)
- * @param userhash_support if set to non-zero value (#MHD_YES) then support of
- *                         userhash is indicated, allowing client to provide
+ *             then one challenge for each allowed algorithm is added
+ * @param userhash_support if set to #MHD_YES then support of userhash is
+ *                         indicated, allowing client to provide
  *                         hash("username:realm") instead of the username in
  *                         clear text;
  *                         note that clients are allowed to provide the username
@@ -3382,24 +3387,265 @@ MHD_FN_PAR_CSTR_ (4);
  * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is
  *                    added, indicating for the client that UTF-8 encoding for
  *                    the username is preferred
- * @return pointer to the action on success,
- *         NULL on failure
+ * @return #MHD_SC_OK if succeed,
+ *         #MHD_SC_TOO_LATE if the response has been already "frozen" (used to
+ *         create an action),
+ *         #MHD_SC_RESP_HEADERS_CONFLICT if Digest Authentication "challenge"
+ *         has been added already,
+ *         #MHD_SC_RESP_POINTER_NULL if @a response is NULL,
+ *         #MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE is response status code is wrong,
+ *         #MHD_SC_RESP_HEADER_VALUE_INVALID if @a realm, @a opaque or @a domain
+ *         have wrong characters or zero length (for @a realm),
+ *         #MHD_SC_RESPONSE_HEADER_MEM_ALLOC_FAILED if memory allocation failed,
+ *         or other error code if failed
  * @ingroup authentication
  */
-MHD_EXTERN_ const struct MHD_Action *
-MHD_action_digest_auth_required_response (
-  struct MHD_Request *request,
-  const char *realm,
-  const char *opaque,
-  const char *domain,
-  struct MHD_Response *response,
-  enum MHD_Bool signal_stale,
+MHD_EXTERN_ enum MHD_StatusCode
+MHD_response_add_auth_digest_challenge (
+  struct MHD_Response *MHD_RESTRICT response,
+  const char *MHD_RESTRICT realm,
+  const char *MHD_RESTRICT opaque,
+  const char *MHD_RESTRICT domain,
+  enum MHD_Bool indicate_stale,
   enum MHD_DigestAuthMultiQOP mqop,
-  enum MHD_DigestAuthMultiAlgo algo,
+  enum MHD_DigestAuthMultiAlgo malgo,
   enum MHD_Bool userhash_support,
   enum MHD_Bool prefer_utf8)
-MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2)
-MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_NONNULL_ (5);
+MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2)
+MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4);
+
+
+#ifndef MHD_NO_STATIC_INLINE
+
+/**
+ * Create action to reply with Digest Authentication "challenge".
+ *
+ * The @a response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code.
+ *
+ * See RFC 7616, section 3.3 for details.
+ *
+ * @param request the request to create the action for
+ * @param realm the realm presented to the client
+ * @param opaque the string for opaque value, can be NULL, but NULL is
+ *               not recommended for better compatibility with clients;
+ *               the recommended format is hex or Base64 encoded string
+ * @param domain the optional space-separated list of URIs for which the
+ *               same authorisation could be used, URIs can be in form
+ *               "path-absolute" (the path for the same host with initial slash)
+ *               or in form "absolute-URI" (the full path with protocol), in
+ *               any case client may assume that URI is in the same "protection
+ *               space" if it starts with any of values specified here;
+ *               could be NULL (clients typically assume that the same
+ *               credentials could be used for any URI on the same host);
+ *               this list provides information for the client only and does
+ *               not actually restrict anything on the server side
+ * @param indicate_stale if set to #MHD_YES then indication of stale nonce used
+ *                       in the client's request is indicated by adding
+ *                       'stale=true' to the authentication header, this
+ *                       instructs the client to retry immediately with the new
+ *                       nonce and the same credentials, without asking user
+ *                       for the new password
+ * @param mqop the QOP to use
+ * @param algo digest algorithm to use; if several algorithms are allowed
+ *             then one challenge for each allowed algorithm is added
+ * @param userhash_support if set to #MHD_YES then support of userhash is
+ *                         indicated, allowing client to provide
+ *                         hash("username:realm") instead of the username in
+ *                         clear text;
+ *                         note that clients are allowed to provide the username
+ *                         in cleartext even if this parameter set to non-zero;
+ *                         when userhash is used, application must be ready to
+ *                         identify users by provided userhash value instead of
+ *                         username; see #MHD_digest_auth_calc_userhash() and
+ *                         #MHD_digest_auth_calc_userhash_hex()
+ * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is
+ *                    added, indicating for the client that UTF-8 encoding for
+ *                    the username is preferred
+ * @param response the response to update; should contain the "access denied"
+ *                 body;
+ *                 note: this function sets the "WWW Authenticate" header and
+ *                 the caller should not set this header;
+ *                 the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status
+ *                 code;
+ *                 the NULL is tolerated (the result is
+ *                 #MHD_SC_RESP_POINTER_NULL)
+ * @param abort_if_failed if set to #MHD_NO the response will be used even if
+ *                        failed to add Basic Authentication "challenge",
+ *                        if not set to #MHD_NO the request will be aborted
+ *                        if the "challenge" could not be added.
+ * @return pointer to the action, the action must be consumed
+ *         otherwise response object may leak;
+ *         NULL if failed or if any action has been already created for
+ *         the @a request;
+ *         when failed the response object is consumed and need not
+ *         to be "destroyed"
+ * @ingroup authentication
+ */
+MHD_STATIC_INLINE_
+MHD_FN_PAR_NONNULL_ (1)
+MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2)
+const struct MHD_Action *
+MHD_action_digest_auth_challenge (struct MHD_Request *MHD_RESTRICT request,
+                                  const char *MHD_RESTRICT realm,
+                                  const char *MHD_RESTRICT opaque,
+                                  const char *MHD_RESTRICT domain,
+                                  enum MHD_Bool indicate_stale,
+                                  enum MHD_DigestAuthMultiQOP mqop,
+                                  enum MHD_DigestAuthMultiAlgo malgo,
+                                  enum MHD_Bool userhash_support,
+                                  enum MHD_Bool prefer_utf8,
+                                  struct MHD_Response *MHD_RESTRICT response,
+                                  enum MHD_Bool abort_if_failed)
+{
+  if ((MHD_SC_OK !=
+       MHD_response_add_auth_digest_challenge (response, realm, opaque, domain,
+                                               indicate_stale, mqop, malgo,
+                                               userhash_support, prefer_utf8))
+      && (MHD_NO != abort_if_failed))
+  {
+    MHD_response_destroy (response);
+    return MHD_action_abort_request (request);
+  }
+  return MHD_action_from_response (request, response);
+}
+
+
+MHD_STATIC_INLINE_END_
+
+/**
+ * Create action to reply with Digest Authentication "challenge".
+ *
+ * The @a response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code.
+ *
+ * If the @a response object cannot be extended with the "challenge",
+ * the @a response is used to reply without the "challenge".
+ *
+ * @param request the request to create the action for
+ * @param realm the realm presented to the client
+ * @param opaque the string for opaque value, can be NULL, but NULL is
+ *               not recommended for better compatibility with clients;
+ *               the recommended format is hex or Base64 encoded string
+ * @param domain the optional space-separated list of URIs for which the
+ *               same authorisation could be used, URIs can be in form
+ *               "path-absolute" (the path for the same host with initial slash)
+ *               or in form "absolute-URI" (the full path with protocol), in
+ *               any case client may assume that URI is in the same "protection
+ *               space" if it starts with any of values specified here;
+ *               could be NULL (clients typically assume that the same
+ *               credentials could be used for any URI on the same host);
+ *               this list provides information for the client only and does
+ *               not actually restrict anything on the server side
+ * @param indicate_stale if set to #MHD_YES then indication of stale nonce used
+ *                       in the client's request is indicated by adding
+ *                       'stale=true' to the authentication header, this
+ *                       instructs the client to retry immediately with the new
+ *                       nonce and the same credentials, without asking user
+ *                       for the new password
+ * @param mqop the QOP to use
+ * @param algo digest algorithm to use; if several algorithms are allowed
+ *             then one challenge for each allowed algorithm is added
+ * @param userhash_support if set to #MHD_YES then support of userhash is
+ *                         indicated, allowing client to provide
+ *                         hash("username:realm") instead of the username in
+ *                         clear text;
+ *                         note that clients are allowed to provide the username
+ *                         in cleartext even if this parameter set to non-zero;
+ *                         when userhash is used, application must be ready to
+ *                         identify users by provided userhash value instead of
+ *                         username; see #MHD_digest_auth_calc_userhash() and
+ *                         #MHD_digest_auth_calc_userhash_hex()
+ * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is
+ *                    added, indicating for the client that UTF-8 encoding for
+ *                    the username is preferred
+ * @param response the response to update; should contain the "access denied"
+ *                 body;
+ *                 note: this function sets the "WWW Authenticate" header and
+ *                 the caller should not set this header;
+ *                 the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status
+ *                 code;
+ *                 the NULL is tolerated (the result is
+ *                 #MHD_SC_RESP_POINTER_NULL)
+ * @return pointer to the action, the action must be consumed
+ *         otherwise response object may leak;
+ *         NULL if failed or if any action has been already created for
+ *         the @a request;
+ *         when failed the response object is consumed and need not
+ *         to be "destroyed"
+ * @ingroup authentication
+ */
+#define MHD_action_digest_auth_challenge_p(rq,rlm,opq,dmn,stl,mqop,malgo, \
+                                           uh,utf,resp) \
+        MHD_action_digest_auth_challenge ((rq),(rlm),(opq),(dmn),(stl),(mqop), \
+                                          (malgo),(uh),(utf),(resp),MHD_NO)
+
+
+/**
+ * Create action to reply with Digest Authentication "challenge".
+ *
+ * The @a response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code.
+ *
+ * If the @a response object cannot be extended with the "challenge",
+ * the @a response is aborted.
+ *
+ * @param request the request to create the action for
+ * @param realm the realm presented to the client
+ * @param opaque the string for opaque value, can be NULL, but NULL is
+ *               not recommended for better compatibility with clients;
+ *               the recommended format is hex or Base64 encoded string
+ * @param domain the optional space-separated list of URIs for which the
+ *               same authorisation could be used, URIs can be in form
+ *               "path-absolute" (the path for the same host with initial slash)
+ *               or in form "absolute-URI" (the full path with protocol), in
+ *               any case client may assume that URI is in the same "protection
+ *               space" if it starts with any of values specified here;
+ *               could be NULL (clients typically assume that the same
+ *               credentials could be used for any URI on the same host);
+ *               this list provides information for the client only and does
+ *               not actually restrict anything on the server side
+ * @param indicate_stale if set to #MHD_YES then indication of stale nonce used
+ *                       in the client's request is indicated by adding
+ *                       'stale=true' to the authentication header, this
+ *                       instructs the client to retry immediately with the new
+ *                       nonce and the same credentials, without asking user
+ *                       for the new password
+ * @param mqop the QOP to use
+ * @param algo digest algorithm to use; if several algorithms are allowed
+ *             then one challenge for each allowed algorithm is added
+ * @param userhash_support if set to #MHD_YES then support of userhash is
+ *                         indicated, allowing client to provide
+ *                         hash("username:realm") instead of the username in
+ *                         clear text;
+ *                         note that clients are allowed to provide the username
+ *                         in cleartext even if this parameter set to non-zero;
+ *                         when userhash is used, application must be ready to
+ *                         identify users by provided userhash value instead of
+ *                         username; see #MHD_digest_auth_calc_userhash() and
+ *                         #MHD_digest_auth_calc_userhash_hex()
+ * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is
+ *                    added, indicating for the client that UTF-8 encoding for
+ *                    the username is preferred
+ * @param response the response to update; should contain the "access denied"
+ *                 body;
+ *                 note: this function sets the "WWW Authenticate" header and
+ *                 the caller should not set this header;
+ *                 the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status
+ *                 code;
+ *                 the NULL is tolerated (the result is
+ *                 #MHD_SC_RESP_POINTER_NULL)
+ * @return pointer to the action, the action must be consumed
+ *         otherwise response object may leak;
+ *         NULL if failed or if any action has been already created for
+ *         the @a request;
+ *         when failed the response object is consumed and need not
+ *         to be "destroyed"
+ * @ingroup authentication
+ */
+#define MHD_action_digest_auth_challenge_a(rq,rlm,opq,dmn,stl,mqop,malgo, \
+                                           uh,utf,resp) \
+        MHD_action_digest_auth_challenge ((rq),(rlm),(opq),(dmn),(stl),(mqop), \
+                                          (malgo),(uh),(utf),(resp),MHD_YES)
+
+#endif /* ! MHD_NO_STATIC_INLINE */
 
 
 /**

+ 10 - 0
src/include/microhttpd2_preamble.h.in

@@ -1621,6 +1621,16 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode
    * cannot be provided due to some error or other reasons.
    */
   MHD_SC_INFO_GET_TYPE_UNAVAILALBE = 60204
+  ,
+  /**
+   * The type of the Digest Auth algorithm is unknown or not supported.
+   */
+  MHD_SC_AUTH_DIGEST_ALGO_NOT_SUPPORTED = 60240
+  ,
+  /**
+   * The the Digest Auth QOP value is unknown or not supported.
+   */
+  MHD_SC_AUTH_DIGEST_QOP_NOT_SUPPORTED = 60241
 };
 
 /**

+ 8 - 0
src/mhd2/Makefile.am

@@ -110,6 +110,10 @@ auth_basic_OPTSOURCES = \
   auth_basic.c              auth_basic.h \
   response_auth_basic.c
 
+auth_digest_OPTSOURCES = \
+  mhd_digest_auth_data.h    mhd_auth_digest_hdr.h \
+  response_auth_digest.c    response_auth_digest.h
+
 upgrade_OPTSOURCES = \
   mhd_upgrade.h \
   upgrade_prep.c            upgrade_prep.h \
@@ -146,6 +150,10 @@ if MHD_SUPPORT_AUTH_BASIC
   libmicrohttpd2_la_SOURCES += $(auth_basic_OPTSOURCES)
 endif
 
+if MHD_SUPPORT_AUTH_DIGEST
+  libmicrohttpd2_la_SOURCES += $(auth_digest_OPTSOURCES)
+endif
+
 if MHD_UPGRADE_SUPPORT
   libmicrohttpd2_la_SOURCES += $(upgrade_OPTSOURCES)
 endif

+ 66 - 0
src/mhd2/mhd_auth_digest_hdr.h

@@ -0,0 +1,66 @@
+/*
+  This file is part of GNU libmicrohttpd
+  Copyright (C) 2024 Evgeny Grin (Karlson2k)
+
+  GNU libmicrohttpd is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  GNU libmicrohttpd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+*/
+
+/**
+ * @file src/mhd2/mhd_auth_digest_hdr.h
+ * @brief  The definition of the Digest Auth response header structure
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_AUTH_DIGEST_HDR_H
+#define MHD_AUTH_DIGEST_HDR_H 1
+
+#include "mhd_sys_options.h"
+
+#ifndef MHD_SUPPORT_AUTH_DIGEST
+#error This header should be used only if Digest Auth is build
+#endif
+
+#include "mhd_str_types.h"
+#include "mhd_dlinked_list.h"
+
+struct MHD_Response;                    /* forward declaration */
+struct mhd_RespAuthDigestHeader;        /* forward declaration */
+
+mhd_DLINKEDL_STRUCTS_DEFS (mhd_RespAuthDigestHeader);
+
+/**
+ * The Digest Auth response header (the challenge)
+ */
+struct mhd_RespAuthDigestHeader
+{
+  /**
+   * The complete header string, including the name, the colon, the value and
+   * CRLF terminating chars
+   */
+  struct MHD_String hdr;
+
+  /**
+   * The position of the nonce placeholder in the @a hdr
+   */
+  size_t nonce_pos;
+
+  /**
+   * The links to the other headers for the same response
+   */
+  mhd_DLNKDL_LINKS_TYPE (mhd_RespAuthDigestHeader) auth_d_hdrs;
+};
+
+#endif /* ! MHD_AUTH_DIGEST_HDR_H */

+ 53 - 0
src/mhd2/mhd_digest_auth_data.h

@@ -0,0 +1,53 @@
+/*
+  This file is part of GNU libmicrohttpd
+  Copyright (C) 2024 Evgeny Grin (Karlson2k)
+
+  GNU libmicrohttpd is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  GNU libmicrohttpd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+*/
+
+/**
+ * @file src/mhd2/mhd_digest_auth_data.h
+ * @brief  The macros and other data for Digest Auth
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_DIGEST_AUTH_DATA_H
+#define MHD_DIGEST_AUTH_DATA_H 1
+
+#include "mhd_sys_options.h"
+
+/**
+ * The name of the Auth scheme
+ */
+#define mhd_AUTH_DIGEST_SCHEME "Digest"
+
+/**
+ * The length of the random part of the nonce (in chars)
+ */
+#define mhd_AUTH_DIGEST_NONCE_RAND_LEN  64
+
+/**
+ * The length of the validity time part of the nonce (in chars)
+ */
+#define mhd_AUTH_DIGEST_NONCE_VALD_LEN  8
+
+/**
+ * The total length of the nonce (in chars)
+ */
+#define mhd_AUTH_DIGEST_NONCE_LEN \
+        (mhd_AUTH_DIGEST_NONCE_RAND_LEN + mhd_AUTH_DIGEST_NONCE_VALD_LEN)
+
+#endif /* ! MHD_DIGEST_AUTH_DATA_H */

+ 10 - 0
src/mhd2/mhd_response.h

@@ -36,6 +36,9 @@
 
 #include "mhd_dlinked_list.h"
 #include "mhd_str_types.h"
+#ifdef MHD_SUPPORT_AUTH_DIGEST
+#  include "mhd_auth_digest_hdr.h"
+#endif
 
 #include "mhd_iovec.h"
 
@@ -356,6 +359,13 @@ struct MHD_Response
    */
   mhd_DLNKDL_LIST (mhd_ResponseHeader,headers);
 
+#ifdef MHD_SUPPORT_AUTH_DIGEST
+  /**
+   * The double linked list of the Digest Auth response headers
+   */
+  mhd_DLNKDL_LIST (mhd_RespAuthDigestHeader,auth_d_hdrs);
+#endif
+
   /**
    * Special data for internal error responses
    */

+ 521 - 0
src/mhd2/response_auth_digest.c

@@ -0,0 +1,521 @@
+/*
+  This file is part of GNU libmicrohttpd
+  Copyright (C) 2022-2024 Evgeny Grin (Karlson2k)
+
+  GNU libmicrohttpd is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  GNU libmicrohttpd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+*/
+
+/**
+ * @file src/mhd2/response_auth_digest.c
+ * @brief  The definitions of MHD_response_add_auth_basic_challenge() function
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_sys_options.h"
+
+#include "sys_bool_type.h"
+
+#include "mhd_unreachable.h"
+#include "mhd_assert.h"
+
+#include "mhd_digest_auth_data.h"
+
+#include "mhd_response.h"
+#include "mhd_locks.h"
+#include "mhd_str_types.h"
+#include <string.h>
+#include "sys_malloc.h"
+
+#include "mhd_str_macros.h"
+#include "mhd_str.h"
+
+#include "mhd_public_api.h"
+
+#include "response_auth_digest.h"
+#include "mhd_auth_digest_hdr.h"
+
+
+MHD_INTERNAL
+MHD_FN_PAR_NONNULL_ (1) void
+mhd_response_remove_auth_digest_headers (struct MHD_Response*response)
+{
+  struct mhd_RespAuthDigestHeader *hdr_d;
+
+  for (hdr_d = mhd_DLINKEDL_GET_LAST (response, auth_d_hdrs);
+       NULL != hdr_d;
+       hdr_d = mhd_DLINKEDL_GET_LAST (response, auth_d_hdrs))
+  {
+    mhd_DLINKEDL_DEL (response, hdr_d, auth_d_hdrs);
+    free (hdr_d);
+  }
+}
+
+
+/**
+ * Create and add Digest Auth challenge header with specified algorithm
+ * @param response the response to update
+ * @param rlm the realm to use
+ * @param opq the "opaque" string to use
+ * @param dmn the "domain" string to use
+ * @param indicate_stale whether to indicate "stale" nonce
+ * @param qop_none whether to use RFC 2069 subset only
+ * @param algo the algorithm to use
+ * @param userhash_support whether to indicate support for "userhash"
+ * @param prefer_utf8 whether to indicate UTF-8 support
+ * @return #MHD_SC_OK on success,
+ *         error code otherwise
+ */
+static MHD_FN_PAR_NONNULL_ALL_ enum MHD_StatusCode
+response_add_auth_digest_challenge_alg (
+  struct MHD_Response *restrict response,
+  const struct MHD_String *restrict rlm,
+  const struct MHD_StringNullable *restrict opq,
+  const struct MHD_StringNullable *restrict dmn,
+  enum MHD_Bool indicate_stale,
+  bool qop_none,
+  enum MHD_DigestAuthAlgo algo,
+  enum MHD_Bool userhash_support,
+  enum MHD_Bool prefer_utf8)
+{
+  static const struct MHD_String empty_str =
+    mhd_MSTR_INIT ("");
+  static const struct MHD_String hdr_pref_realm_pref =
+    mhd_MSTR_INIT (MHD_HTTP_HEADER_WWW_AUTHENTICATE ": " \
+                   mhd_AUTH_DIGEST_SCHEME " realm=\"");
+  static const struct MHD_String qop_str =
+    mhd_MSTR_INIT (", qop=\"auth\"");
+#ifdef MHD_SUPPORT_MD5
+  static const struct MHD_String algo_mh5_str =
+    mhd_MSTR_INIT (", algorithm=MD5");
+#endif
+#ifdef MHD_SUPPORT_SHA256
+  static const struct MHD_String algo_sha256_str =
+    mhd_MSTR_INIT (", algorithm=SHA-256");
+#endif
+#ifdef MHD_SUPPORT_SHA512_256
+  static const struct MHD_String algo_sha512_256_str =
+    mhd_MSTR_INIT (", algorithm=SHA-512-256");
+#endif
+  static const struct MHD_String nonce_str =
+    mhd_MSTR_INIT (", nonce=\"" \
+                   "00000000000000000000000000000000" \
+                   "00000000000000000000000000000000" \
+                   "00000000" \
+                   "\"");
+  static size_t nonce_off = 9; /* Position of nonce value in the nonce_str */
+  static const struct MHD_String opaque_pref =
+    mhd_MSTR_INIT (", opaque=\"");
+  static const struct MHD_String domain_pref =
+    mhd_MSTR_INIT (", domain=\"");
+  static const struct MHD_String stale_str =
+    mhd_MSTR_INIT (", stale=true");
+  static const struct MHD_String charset_str =
+    mhd_MSTR_INIT (", charset=UTF-8");
+  static const struct MHD_String userhash_str =
+    mhd_MSTR_INIT (", userhash=true");
+
+  /* Header content:
+     + Scheme name and space;
+     + realm, quoted;
+     + qop="auth", quoted (optional);
+     + algorithm, NOT quoted (optional);
+     + nonce (placeholder), quoted;
+     + opaque, quoted (optional);
+     + domain, quoted (optional);
+     + stale=true (optional);
+     + charset=UTF-8 (optional);
+     + userhash=true (optional).
+   */
+
+  struct MHD_String algo_str;
+  char *hdr_str;
+  size_t hdr_maxlen;
+  struct mhd_RespAuthDigestHeader *new_hdr;
+  size_t pos;
+  size_t elm_len;
+
+  mhd_assert ('0' == nonce_str.cstr[nonce_off]);
+  mhd_assert ('0' == nonce_str.cstr[nonce_off + mhd_AUTH_DIGEST_NONCE_LEN]);
+  mhd_assert ('"' == nonce_str.cstr[nonce_off - 1]);
+  mhd_assert ('"' == nonce_str.cstr[nonce_off + mhd_AUTH_DIGEST_NONCE_LEN + 1]);
+
+#ifdef MHD_SUPPORT_MD5
+  if (MHD_DIGEST_AUTH_ALGO_MD5 == algo)
+    algo_str = qop_none ? empty_str : algo_mh5_str;
+#endif
+#ifdef MHD_SUPPORT_SHA256
+  else if (MHD_DIGEST_AUTH_ALGO_SHA256 == algo)
+    algo_str = algo_sha256_str;
+#endif
+#ifdef MHD_SUPPORT_SHA512_256
+  else if (MHD_DIGEST_AUTH_ALGO_SHA512_256 == algo)
+    algo_str = algo_sha512_256_str;
+#endif
+  else
+    mhd_UNREACHABLE ();
+
+  /* ** Calculate the maximum length of the header string ** */
+  hdr_maxlen = 0;
+
+  /* realm */
+  hdr_maxlen += hdr_pref_realm_pref.len;
+  hdr_maxlen += rlm->len * 2; /* Double length for quoting */
+  hdr_maxlen += 1; /* closing quote (") */
+
+  /* qop */
+  hdr_maxlen += qop_str.len;
+
+  /* algorithm */
+  hdr_maxlen += algo_str.len;
+
+  /* nonce */
+  hdr_maxlen += nonce_str.len;
+
+  /* opaque */
+  hdr_maxlen += opaque_pref.len;
+  hdr_maxlen += opq->len * 2; /* Double length for quoting */
+  hdr_maxlen += 1; /* closing quote (") */
+
+  /* domain */
+  hdr_maxlen += domain_pref.len;
+  hdr_maxlen += dmn->len;
+  hdr_maxlen += 1; /* closing quote (") */
+
+  /* stale */
+  hdr_maxlen += stale_str.len;
+
+  /* charset */
+  hdr_maxlen += charset_str.len;
+
+  /* userhash */
+  hdr_maxlen += userhash_str.len;
+
+  /* CRLF */
+  hdr_maxlen += 2;
+
+  /* ** Allocate ** */
+  new_hdr = (struct mhd_RespAuthDigestHeader *)
+            malloc (sizeof(struct mhd_RespAuthDigestHeader *)
+                    + hdr_maxlen + 1);
+  if (NULL == new_hdr)
+    return MHD_SC_RESPONSE_HEADER_MEM_ALLOC_FAILED;
+  hdr_str = (char *) (new_hdr + 1);
+
+  /* ** Build the header ** */
+  pos = 0;
+
+  /* realm */
+  memcpy (hdr_str + pos,
+          hdr_pref_realm_pref.cstr,
+          hdr_pref_realm_pref.len);
+  pos += hdr_pref_realm_pref.len;
+  elm_len = mhd_str_quote (rlm->cstr,
+                           rlm->len,
+                           hdr_str + pos,
+                           hdr_maxlen - pos);
+  mhd_assert (0 != elm_len);
+  pos += elm_len;
+  hdr_str[pos++] = '"';
+
+  /* qop */
+  if (! qop_none)
+  {
+    memcpy (hdr_str + pos,
+            qop_str.cstr,
+            qop_str.len);
+    pos += qop_str.len;
+  }
+
+  /* algorithm */
+  if (0 != algo_str.len)
+  {
+    memcpy (hdr_str + pos,
+            algo_str.cstr,
+            algo_str.len);
+    pos += algo_str.len;
+  }
+
+  /* nonce */
+  memcpy (hdr_str + pos,
+          nonce_str.cstr,
+          nonce_str.len);
+  new_hdr->nonce_pos = pos + nonce_off;
+  pos += nonce_str.len;
+
+  /* opaque */
+  if (0 != opq->len)
+  {
+    memcpy (hdr_str + pos,
+            opaque_pref.cstr,
+            opaque_pref.len);
+    pos += opaque_pref.len;
+    elm_len = mhd_str_quote (opq->cstr,
+                             opq->len,
+                             hdr_str + pos,
+                             hdr_maxlen - pos);
+    mhd_assert (0 != elm_len);
+    pos += elm_len;
+    hdr_str[pos++] = '"';
+  }
+
+  /* domain */
+  if (0 != dmn->len)
+  {
+    memcpy (hdr_str + pos,
+            domain_pref.cstr,
+            domain_pref.len);
+    pos += domain_pref.len;
+    memcpy (hdr_str + pos,
+            dmn->cstr,
+            dmn->len);
+    pos += dmn->len;
+    hdr_str[pos++] = '"';
+  }
+
+  /* stale */
+  if (MHD_NO != indicate_stale)
+  {
+    memcpy (hdr_str + pos,
+            stale_str.cstr,
+            stale_str.len);
+    pos += stale_str.len;
+  }
+
+  /* charset */
+  if ((! qop_none) &&
+      (MHD_NO != prefer_utf8))
+  {
+    memcpy (hdr_str + pos,
+            charset_str.cstr,
+            charset_str.len);
+    pos += charset_str.len;
+  }
+
+  /* userhash */
+  if ((! qop_none) &&
+      (MHD_NO != userhash_support))
+  {
+    memcpy (hdr_str + pos,
+            userhash_str.cstr,
+            userhash_str.len);
+    pos += userhash_str.len;
+  }
+
+  /* CRLF */
+  hdr_str[pos++] = '\r';
+  hdr_str[pos++] = '\n';
+
+  mhd_assert (pos <= hdr_maxlen);
+  hdr_str[pos] = 0; /* Zero-terminate the string */
+
+  if (1)
+  { /* Try to shrink malloc'ed area */
+    void *new_ptr;
+    new_ptr = realloc (new_hdr,
+                       sizeof(struct mhd_RespAuthDigestHeader)
+                       + pos);
+    /* Just use the old pointer if realloc() failed */
+    if (NULL != new_ptr)
+      new_hdr = (struct mhd_RespAuthDigestHeader *) new_ptr;
+  }
+
+  new_hdr->hdr.cstr = (char *) (new_hdr + 1);
+  new_hdr->hdr.len = pos;
+  mhd_assert (0 == \
+              memcmp (new_hdr->hdr.cstr, \
+                      MHD_HTTP_HEADER_WWW_AUTHENTICATE ": ", \
+                      mhd_SSTR_LEN (MHD_HTTP_HEADER_WWW_AUTHENTICATE ": ")));
+  mhd_assert (0 == new_hdr->hdr.cstr[new_hdr->hdr.len]);
+
+  mhd_DLINKEDL_INIT_LINKS (new_hdr, auth_d_hdrs);
+  mhd_DLINKEDL_INS_LAST (response, new_hdr, auth_d_hdrs);
+
+  return MHD_SC_OK;
+}
+
+
+/**
+ * Create and add Digest Auth challenge headers for all specified algorithms
+ * @param response the response to update
+ * @param realm the real to use
+ * @param opaque the "opaque" string, could be NULL
+ * @param domain the "domain" string, could be NULL
+ * @param indicate_stale whether to indicate "stale" nonce
+ * @param mqop the QOP values to use
+ * @param malgo the algorithms to use
+ * @param userhash_support whether to indicate support for "userhash"
+ * @param prefer_utf8 whether to indicate UTF-8 support
+ * @return #MHD_SC_OK on success,
+ *         error code otherwise
+ */
+static MHD_FN_PAR_NONNULL_ (1)
+MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2)
+MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) enum MHD_StatusCode
+response_add_auth_digest_challenge_int (struct MHD_Response *restrict response,
+                                        const char *restrict realm,
+                                        const char *restrict opaque,
+                                        const char *restrict domain,
+                                        enum MHD_Bool indicate_stale,
+                                        enum MHD_DigestAuthMultiQOP mqop,
+                                        enum MHD_DigestAuthMultiAlgo malgo,
+                                        enum MHD_Bool userhash_support,
+                                        enum MHD_Bool prefer_utf8)
+{
+  const struct MHD_String rlm = { strlen (realm), realm };
+  const struct MHD_StringNullable opq =
+  { (NULL != opaque ? strlen (opaque) : 0), opaque };
+  const struct MHD_StringNullable dmn =
+  { (NULL != domain ? strlen (domain) : 0), domain };
+  const bool qop_none =
+    (0 != (MHD_DIGEST_AUTH_QOP_NONE & ((unsigned int) mqop)));
+  enum MHD_StatusCode res;
+
+  /* Check validity of the input data */
+
+  if (0 == rlm.len)
+    return MHD_SC_RESP_HEADER_VALUE_INVALID;   /* Failure exit point */
+  if ((NULL != memchr (rlm.cstr, '\n', rlm.len)) ||
+      (NULL != memchr (rlm.cstr, '\r', rlm.len)))
+    return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */
+
+  if ((0 != opq.len) &&
+      ((NULL != memchr (opq.cstr, '\n', opq.len)) ||
+       (NULL != memchr (opq.cstr, '\r', opq.len))))
+    return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */
+
+  if ((0 != dmn.len) &&
+      ((NULL != memchr (dmn.cstr, '\n', dmn.len)) ||
+       (NULL != memchr (dmn.cstr, '\r', dmn.len)) ||
+       (NULL != memchr (dmn.cstr, '"', dmn.len))))
+    return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */
+
+  if (0 == (MHD_DIGEST_AUTH_ALGO_NON_SESSION & ((unsigned int) malgo)))
+    return MHD_SC_AUTH_DIGEST_ALGO_NOT_SUPPORTED; /* Failure exit point */
+
+  if (0 == ((MHD_DIGEST_AUTH_QOP_NONE | MHD_DIGEST_AUTH_QOP_AUTH)
+            & ((unsigned int) mqop)))
+    return MHD_SC_AUTH_DIGEST_QOP_NOT_SUPPORTED; /* Failure exit point */
+
+  res = MHD_SC_OK;
+
+#ifdef MHD_SUPPORT_MD5
+  if ((MHD_SC_OK == res) &&
+      (0 == (MHD_DIGEST_BASE_ALGO_MD5 & ((unsigned int) malgo))))
+    res = response_add_auth_digest_challenge_alg (response,
+                                                  &rlm,
+                                                  &opq,
+                                                  &dmn,
+                                                  indicate_stale,
+                                                  qop_none,
+                                                  MHD_DIGEST_AUTH_ALGO_MD5,
+                                                  userhash_support,
+                                                  prefer_utf8);
+#endif
+#ifdef MHD_SUPPORT_SHA256
+  if ((MHD_SC_OK == res) &&
+      (0 == (MHD_DIGEST_BASE_ALGO_SHA256 & ((unsigned int) malgo))))
+    res = response_add_auth_digest_challenge_alg (response,
+                                                  &rlm,
+                                                  &opq,
+                                                  &dmn,
+                                                  indicate_stale,
+                                                  qop_none,
+                                                  MHD_DIGEST_AUTH_ALGO_SHA256,
+                                                  userhash_support,
+                                                  prefer_utf8);
+#endif
+#ifdef MHD_SUPPORT_SHA512_256
+  if ((MHD_SC_OK == res) &&
+      (0 == (MHD_DIGEST_BASE_ALGO_SHA256 & ((unsigned int) malgo))))
+    res = response_add_auth_digest_challenge_alg (
+      response,
+      &rlm,
+      &opq,
+      &dmn,
+      indicate_stale,
+      qop_none,
+      MHD_DIGEST_AUTH_ALGO_SHA512_256,
+      userhash_support,
+      prefer_utf8);
+#endif
+
+  if (MHD_SC_OK != res)
+  {
+    mhd_response_remove_auth_digest_headers (response);
+    return res; /* Failure exit point */
+  }
+
+  if (NULL == mhd_DLINKEDL_GET_FIRST (response, auth_d_hdrs))
+    return MHD_SC_AUTH_DIGEST_ALGO_NOT_SUPPORTED;  /* Failure exit point */
+
+  return MHD_SC_OK; /* Success exit point */
+}
+
+
+MHD_EXTERN_
+MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2)
+MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) enum MHD_StatusCode
+MHD_response_add_auth_digest_challenge (
+  struct MHD_Response *MHD_RESTRICT response,
+  const char *MHD_RESTRICT realm,
+  const char *MHD_RESTRICT opaque,
+  const char *MHD_RESTRICT domain,
+  enum MHD_Bool indicate_stale,
+  enum MHD_DigestAuthMultiQOP mqop,
+  enum MHD_DigestAuthMultiAlgo malgo,
+  enum MHD_Bool userhash_support,
+  enum MHD_Bool prefer_utf8)
+{
+  bool need_unlock;
+  enum MHD_StatusCode res;
+
+  if (NULL == response)
+    return MHD_SC_RESP_POINTER_NULL;
+  if (response->frozen)
+    return MHD_SC_TOO_LATE;
+  if (MHD_HTTP_STATUS_UNAUTHORIZED != response->sc)
+    return MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE;
+
+  if (response->reuse.reusable)
+  {
+    need_unlock = true;
+    if (! mhd_mutex_lock (&(response->reuse.settings_lock)))
+      return MHD_SC_RESPONSE_MUTEX_LOCK_FAILED;
+    mhd_assert (1 == mhd_atomic_counter_get (&(response->reuse.counter)));
+  }
+  else
+    need_unlock = false;
+
+  if (response->frozen) /* Re-check with the lock held */
+    res = MHD_SC_TOO_LATE;
+  else if (NULL != mhd_DLINKEDL_GET_FIRST (response, auth_d_hdrs))
+    res = MHD_SC_RESP_HEADERS_CONFLICT;
+  else
+    res = response_add_auth_digest_challenge_int (response,
+                                                  realm,
+                                                  opaque,
+                                                  domain,
+                                                  indicate_stale,
+                                                  mqop,
+                                                  malgo,
+                                                  userhash_support,
+                                                  prefer_utf8);
+
+  if (need_unlock)
+    mhd_mutex_unlock_chk (&(response->reuse.settings_lock));
+
+  return res;
+}

+ 42 - 0
src/mhd2/response_auth_digest.h

@@ -0,0 +1,42 @@
+/*
+  This file is part of GNU libmicrohttpd
+  Copyright (C) 2024 Evgeny Grin (Karlson2k)
+
+  GNU libmicrohttpd is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  GNU libmicrohttpd 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+*/
+
+/**
+ * @file src/mhd2/response_auth_digest.h
+ * @brief  The declaration of the Digest Auth response header helper functions
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#ifndef MHD_RESPONSE_AUTH_DIGEST_H
+#define MHD_RESPONSE_AUTH_DIGEST_H 1
+
+#include "mhd_sys_options.h"
+
+struct MHD_Response;                    /* forward declaration */
+
+/**
+ * Remove all Digest Auth headers (if any) from the response
+ * @param response the response to clean
+ */
+MHD_INTERNAL void
+mhd_response_remove_auth_digest_headers (struct MHD_Response*response)
+MHD_FN_PAR_NONNULL_ (1);
+
+#endif /* ! MHD_RESPONSE_AUTH_DIGEST_H */

+ 7 - 0
src/mhd2/response_destroy.c

@@ -40,6 +40,10 @@
 #include "response_funcs.h"
 #include "response_from.h"
 
+#ifdef MHD_SUPPORT_AUTH_DIGEST
+#  include "response_auth_digest.h"
+#endif
+
 #define mhd_RESPONSE_DESTOYED "Attempt to use destroyed response, " \
         "re-use non-reusable response or wrong MHD_Response pointer"
 
@@ -52,6 +56,9 @@
 static MHD_FN_PAR_NONNULL_ (1) void
 response_full_deinit (struct MHD_Response *restrict r)
 {
+#ifdef MHD_SUPPORT_AUTH_DIGEST
+  mhd_response_remove_auth_digest_headers (r);
+#endif
   mhd_response_remove_all_headers (r);
   if (NULL != r->special_resp.spec_hdr)
     free (r->special_resp.spec_hdr);

+ 3 - 0
src/mhd2/response_from.c

@@ -68,6 +68,9 @@ response_create_basic (enum MHD_HTTP_StatusCode sc,
     {
 #ifndef HAVE_NULL_PTR_ALL_ZEROS
       mhd_DLINKEDL_INIT_LIST (r, headers);
+#ifdef MHD_SUPPORT_AUTH_DIGEST
+      mhd_DLINKEDL_INIT_LIST (r, auth_d_hdrs);
+#endif
       r->free.cb = NULL;
       r->free.cls = NULL;
       r->special_resp.spec_hdr = NULL;

+ 10 - 10
src/microhttpd/Makefile.am

@@ -175,10 +175,10 @@ if MHD_SUPPORT_AUTH_DIGEST
 libmicrohttpd_la_SOURCES += \
   digestauth.c digestauth.h \
   mhd_bithelpers.h mhd_byteorder.h mhd_align.h
-if ENABLE_MD5
+if MHD_SUPPORT_MD5
 libmicrohttpd_la_SOURCES += \
   mhd_md5_wrap.h
-if ! ENABLE_MD5_EXT
+if ! MHD_MD5_EXTR
 libmicrohttpd_la_SOURCES += \
   md5.c md5.h
 else
@@ -186,10 +186,10 @@ libmicrohttpd_la_SOURCES += \
   md5_ext.c md5_ext.h
 endif
 endif
-if ENABLE_SHA256
+if MHD_SUPPORT_SHA256
 libmicrohttpd_la_SOURCES += \
   mhd_sha256_wrap.h
-if ! ENABLE_SHA256_EXT
+if ! MHD_SHA256_EXTR
 libmicrohttpd_la_SOURCES += \
   sha256.c sha256.h
 else
@@ -197,7 +197,7 @@ libmicrohttpd_la_SOURCES += \
   sha256_ext.c sha256_ext.h
 endif
 endif
-if ENABLE_SHA512_256
+if MHD_SUPPORT_SHA512_256
 libmicrohttpd_la_SOURCES += \
   sha512_256.c sha512_256.h
 endif
@@ -244,15 +244,15 @@ check_PROGRAMS = \
   test_mhd_version \
   test_set_panic
 
-if ENABLE_MD5
+if MHD_SUPPORT_MD5
 check_PROGRAMS += \
   test_md5
 endif
-if ENABLE_SHA256
+if MHD_SUPPORT_SHA256
 check_PROGRAMS += \
   test_sha256
 endif
-if ENABLE_SHA512_256
+if MHD_SUPPORT_SHA512_256
 check_PROGRAMS += \
   test_sha512_256
 endif
@@ -518,7 +518,7 @@ test_http_reasons_SOURCES = \
 
 test_md5_SOURCES = \
   test_md5.c test_helpers.h mhd_md5_wrap.h ../include/mhd_options.h
-if ! ENABLE_MD5_EXT
+if ! MHD_MD5_EXTR
 test_md5_SOURCES += \
   md5.c md5.h mhd_bithelpers.h mhd_byteorder.h mhd_align.h
 test_md5_CPPFLAGS = $(AM_CPPFLAGS)
@@ -536,7 +536,7 @@ endif
 
 test_sha256_SOURCES = \
   test_sha256.c test_helpers.h mhd_sha256_wrap.h ../include/mhd_options.h
-if ! ENABLE_SHA256_EXT
+if ! MHD_SHA256_EXTR
 test_sha256_SOURCES += \
   sha256.c sha256.h mhd_bithelpers.h mhd_byteorder.h mhd_align.h
 test_sha256_CPPFLAGS = $(AM_CPPFLAGS)

+ 4 - 4
src/testcurl/Makefile.am

@@ -181,18 +181,18 @@ endif
 
 
 if MHD_SUPPORT_AUTH_DIGEST
-if ENABLE_MD5
+if MHD_SUPPORT_MD5
 THREAD_ONLY_TESTS += \
   test_digestauth \
   test_digestauth_with_arguments \
   test_digestauth_concurrent
 endif
-if ENABLE_SHA256
+if MHD_SUPPORT_SHA256
 THREAD_ONLY_TESTS += \
   test_digestauth_sha256
 endif
 
-if ENABLE_MD5
+if MHD_SUPPORT_MD5
 check_PROGRAMS += \
   test_digestauth_emu_ext \
   test_digestauth_emu_ext_oldapi \
@@ -211,7 +211,7 @@ check_PROGRAMS += \
   test_digestauth2_oldapi1_bind_all \
   test_digestauth2_oldapi1_bind_uri
 endif
-if ENABLE_SHA256
+if MHD_SUPPORT_SHA256
 check_PROGRAMS += \
   test_digestauth2_sha256 \
   test_digestauth2_sha256_userhash \