libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit 98b3c68aabae5e9a095e665f72a3bde209ed8a52
parent c80ceb0d0261f271c6b87d62aed433ae37e1f50f
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
Date:   Wed, 22 Jun 2022 16:37:14 +0300

Added new functions MHD_digest_auth_get_request_info3() and MHD_digest_auth_get_username3()

Diffstat:
Msrc/include/microhttpd.h | 328++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/microhttpd/digestauth.c | 568+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 895 insertions(+), 1 deletion(-)

diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -96,7 +96,7 @@ extern "C" * they are parsed as decimal numbers. * Example: 0x01093001 = 1.9.30-1. */ -#define MHD_VERSION 0x00097518 +#define MHD_VERSION 0x00097519 /* If generic headers don't work on your platform, include headers which define 'va_list', 'size_t', 'ssize_t', 'intptr_t', 'off_t', @@ -4334,6 +4334,331 @@ MHD_destroy_post_processor (struct MHD_PostProcessor *pp); */ #define MHD_INVALID_NONCE -1 +/** + * The flag indicating non-session algorithm types, + * like 'MD5' or 'SHA-256'. + * @note Available since #MHD_VERSION 0x00097519 + */ +#define MHD_DIGEST_AUTH_ALGO3_NON_SESSION (1 << 6) + +/** + * The flag indicating session algorithm types, + * like 'MD5-sess' or 'SHA-256-sess'. + * @note Available since #MHD_VERSION 0x00097519 + */ +#define MHD_DIGEST_AUTH_ALGO3_SESSION (1 << 7) + +/** + * Digest algorithm identification + * @warning Do not be confused with #MHD_DigestAuthAlgorithm, + * which uses other values! + * @note Available since #MHD_VERSION 0x00097519 + */ +enum MHD_DigestAuthAlgo3 +{ + /** + * Unknown or wrong algorithm type. + * Used in struct MHD_DigestAuthInfo to indicate client value that + * cannot by identified. + */ + MHD_DIGEST_AUTH_ALGO3_INVALID = 0, + /** + * The 'MD5' algorithm. + */ + MHD_DIGEST_AUTH_ALGO3_MD5 = + (1 << 0) | MHD_DIGEST_AUTH_ALGO3_NON_SESSION, + /** + * The 'MD5-sess' algorithm. + * Not supported by MHD. + */ + MHD_DIGEST_AUTH_ALGO3_MD5_SESSION = + (1 << 0) | MHD_DIGEST_AUTH_ALGO3_SESSION, + /** + * The 'SHA-256' algorithm. + */ + MHD_DIGEST_AUTH_ALGO3_SHA256 = + (1 << 1) | MHD_DIGEST_AUTH_ALGO3_NON_SESSION, + /** + * The 'SHA-256-sess' algorithm. + * Not supported by MHD. + */ + MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION = + (1 << 1) | MHD_DIGEST_AUTH_ALGO3_SESSION, + /** + * The 'SHA-512-256' (SHA-512/256) algorithm. + * Not supported by MHD. + */ + MHD_DIGEST_AUTH_ALGO3_SHA512_256 = + (1 << 2) | MHD_DIGEST_AUTH_ALGO3_NON_SESSION, + /** + * The 'SHA-512-256-sess' (SHA-512/256 session) algorithm. + * Not supported by MHD. + */ + MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION = + (1 << 2) | MHD_DIGEST_AUTH_ALGO3_SESSION, + /** + * Any non-session algorithm, MHD will choose. + */ + MHD_DIGEST_AUTH_ALGO3_ANY_NON_SESSION = + (0x3F) | MHD_DIGEST_AUTH_ALGO3_NON_SESSION, + /** + * Any session algorithm, MHD will choose. + * Not supported by MHD. + */ + MHD_DIGEST_AUTH_ALGO3_ANY_SESSION = + (0x3F) | MHD_DIGEST_AUTH_ALGO3_SESSION, + /** + * Any algorithm, MHD will choose. + */ + MHD_DIGEST_AUTH_ALGO3_ANY = + (0x3F) | MHD_DIGEST_AUTH_ALGO3_NON_SESSION | MHD_DIGEST_AUTH_ALGO3_SESSION +} _MHD_FLAGS_ENUM; + +/** + * The type of username used by client in Digest Authorization header + * + * @note Available since #MHD_VERSION 0x00097519 + */ +enum MHD_DigestAuthUsernameType +{ + /** + * No username parameter in in Digest Authorization header. + * This should be treated as an error. + */ + MHD_DIGEST_AUTH_UNAME_TYPE_MISSING = 0, + /** + * The 'username' parameter is used to specify the username. + */ + MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD = 1, + /** + * The username is specified by 'username*' parameter with + * the extended notation (see RFC 5987 #section-3.2.1). + * The only difference between standard and extended types is + * the way how username value is encoded in the header. + */ + MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED = 2, + /** + * The username provided in form of 'userhash' as + * specified by RFC 7616 #section-3.4.4. + */ + MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH = 3, + /** + * The invalid combination of username parameters are used by client. + * Either: + * * both 'username' and 'username*' are used + * * 'username*' is used with 'userhash=true' + * * 'username*' used with invalid extended notation + * * 'username' is not hexadecimal digits, while 'userhash' set to 'true' + */ + MHD_DIGEST_AUTH_UNAME_TYPE_INVALID = 15 +} _MHD_FIXED_ENUM; + +/** + * The QOP ('quality of protection') types. + * @note Available since #MHD_VERSION 0x00097519 + */ +enum MHD_DigestAuthQOP +{ + /** + * Invalid/unknown QOP. + * Used in struct MHD_DigestAuthInfo to indicate client value that + * cannot by identified. + */ + MHD_DIGEST_AUTH_QOP_INVALID = 0, + /** + * No QOP value. + */ + MHD_DIGEST_AUTH_QOP_NONE = 1 << 0, + /** + * The 'auth' QOP type. + */ + MHD_DIGEST_AUTH_QOP_AUTH = 1 << 1, + /** + * The 'auth-int' QOP type. + * Not supported by MHD. + */ + MHD_DIGEST_AUTH_QOP_AUTH_INT = 1 << 2 +} _MHD_FLAGS_ENUM; + +/** + * The invalid value of 'nc' parameter in client Digest Authorization header. + * @note Available since #MHD_VERSION 0x00097519 + */ +#define MHD_DIGEST_AUTH_INVALID_NC_VALUE (0) + +/** + * Information from Digest Authorization client's header. + * + * All buffers pointed by any struct members are freed when #MHD_free() is + * called for pointer to this structure. + * + * Application may modify buffers as needed until #MHD_free() is called for + * pointer to this structure + * @note Available since #MHD_VERSION 0x00097519 + */ +struct MHD_DigestAuthInfo +{ + /** + * The algorithm as defined by client. + * Set automatically to MD5 if not specified by client. + * No "group" (ALGO3_ANY) values are used. + * @warning Do not be confused with #MHD_DigestAuthAlgorithm, + * which uses other values! + */ + enum MHD_DigestAuthAlgo3 algo; + /** + * The type of username used by client. + */ + enum MHD_DigestAuthUsernameType uname_type; + /** + * The username string. + * Valid only if username is standard, extended, or userhash. + * For userhash this is unqoted string without decoding of the + * hexadecimal digits (as provided by client). + * If extended notation is used, this string is pct-decoded string + * with charset and language tag removed (i.e. it is original username + * extracted from the extended notation). + * This can be NULL is username is missing or invalid. + */ + char *username; + /** + * The length of the @a username. + * When the @a username is NULL, this member is always zero. + */ + size_t username_len; + /** + * The userhash decoded to binary form. + * Used only if username type is userhash, always NULL otherwise. + * @warning this is a binary data, no zero termination + */ + uint8_t *userhash_bin; + /** + * The number of bytes pointed by the @a userhash_bin. + * When the @a userhash_bin is NULL, this member is always zero. + */ + size_t userhash_bin_size; + /** + * The 'opaque' parameter value, as specified by client. + * NULL if not specified by client. + */ + char *opaque; + /** + * The length of the @a opaque. + * When the @a opaque is NULL, this member is always zero. + */ + size_t opaque_len; + /** + * The 'realm' parameter value, as specified by client. + * NULL if not specified by client. + */ + char *realm; + /** + * The length of the @a realm. + * When the @a realm is NULL, this member is always zero. + */ + size_t realm_len; + /** + * The 'qop' parameter value. + */ + enum MHD_DigestAuthQOP qop; + /** + * The length of the 'cnonce' parameter value, including possible + * backslash-escape characters. + * 'cnonce' is used in hash calculation, which is CPU-intensive procedure. + * An applicaion may want to reject too large cnonces to limit the CPU load. + * A few kilobytes is a reasonable limit, typically cnonce is just 32-160 + * characters long. + */ + size_t cnonce_len; + /** + * The nc parameter value. + * Can be used by application to limit the number of nonce re-uses. If @ nc + * is higher than application wants to allow, then fail response with + * 'stale=true' could be used to ask force client to get the fresh 'nonce'. + * If not specified by client or does not have hexadecimal digits only, the + * value is #MHD_DIGEST_AUTH_INVALID_NC_VALUE. + */ + uint32_t nc; +}; + +/** + * Get information about Digest Authorization client's header. + * + * @param connection The MHD connection structure + * @return NULL if no valid Digest Authorization header is used in the request; + * a pointer to the structure with information if the valid request + * header found, free using #MHD_free(). + * @note Available since #MHD_VERSION 0x00097519 + * @ingroup authentication + */ +_MHD_EXTERN struct MHD_DigestAuthInfo * +MHD_digest_auth_get_request_info3 (struct MHD_Connection *connection); + + +/** + * Information from Digest Authorization client's header. + * + * All buffers pointed by any struct members are freed when #MHD_free() is + * called for pointer to this structure. + * + * Application may modify buffers as needed until #MHD_free() is called for + * pointer to this structure + * @note Available since #MHD_VERSION 0x00097519 + */ +struct MHD_DigestAuthUsernameInfo +{ + /** + * The type of username used by client. + * The 'invalid' and 'missing' types are not used in this structure, + * instead NULL is returned by #MHD_digest_auth_get_username3(). + */ + enum MHD_DigestAuthUsernameType uname_type; + /** + * The username string. + * Valid only if username is standard, extended, or userhash. + * For userhash this is unqoted string without decoding of the + * hexadecimal digits (as provided by client). + * If extended notation is used, this string is pct-decoded string + * with charset and language tag removed (i.e. it is original username + * extracted from the extended notation). + * This can be NULL is username is missing or invalid. + */ + char *username; + /** + * The length of the @a username. + * When the @a username is NULL, this member is always zero. + */ + size_t username_len; + /** + * The userhash decoded to binary form. + * Used only if username type is userhash, always NULL if not used. + * @warning this is a binary data, no zero termination + */ + uint8_t *userhash_bin; + /** + * The number of bytes pointed by the @a userhash_bin. + * When the @a userhash_bin is NULL, this member is always zero. + */ + size_t userhash_bin_size; +}; + +/** + * Get the username from Digest Authorization client's header. + * + * @param connection The MHD connection structure + * @return NULL if no valid Digest Authorization header is used in the request, + * or no username parameter is present in the header, or username is + * provided incorrectly by client (see description for + * #MHD_DIGEST_AUTH_UNAME_TYPE_INVALID); + * a pointer structure with information if the valid request header + * found, free using #MHD_free(). + * @sa MHD_digest_auth_get_request_info3() provides more complete information + * @note Available since #MHD_VERSION 0x00097519 + * @ingroup authentication + */ +_MHD_EXTERN struct MHD_DigestAuthUsernameInfo * +MHD_digest_auth_get_username3 (struct MHD_Connection *connection); + /** * Get the username from the authorization header sent by the client @@ -4341,6 +4666,7 @@ MHD_destroy_post_processor (struct MHD_PostProcessor *pp); * @param connection The MHD connection structure * @return NULL if no username could be found, a pointer * to the username if found, free using #MHD_free(). + * @deprecated use MHD_digest_auth_get_username3() * @ingroup authentication */ _MHD_EXTERN char * diff --git a/src/microhttpd/digestauth.c b/src/microhttpd/digestauth.c @@ -147,11 +147,28 @@ #define _MHD_SHA256_TOKEN "SHA-256" /** + * The token for SHA-512/256 algorithm. + * Unsupported currently by MHD for authentication. + */ +#define _MHD_SHA512_256_TOKEN "SHA-512-256" + +/** * The postfix token for "session" algorithms. */ #define _MHD_SESS_TOKEN "-sess" /** + * The required prefix of parameter with the extended notation + */ +#define MHD_DAUTH_EXT_PARAM_PREFIX "UTF-8'" + +/** + * The minimal size of the prefix for parameter with the extended notation + */ +#define MHD_DAUTH_EXT_PARAM_MIN_LEN \ + MHD_STATICSTR_LEN_(MHD_DAUTH_EXT_PARAM_PREFIX "'") + +/** * The result of nonce-nc map array check. */ enum MHD_CheckNonceNC_ @@ -859,6 +876,557 @@ check_nonce_nc (struct MHD_Connection *connection, /** + * Get username type used by the client. + * This function does not check whether userhash can be decoded or + * extended notation (if used) is valid. + * @param params the Digest Authorization parameters + * @return the type of username + */ +_MHD_static_inline enum MHD_DigestAuthUsernameType +get_rq_uname_type (const struct MHD_RqDAuth *params) +{ + if (NULL != params->username.value.str) + { + if (NULL == params->username_ext.value.str) + return params->userhash ? + MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH : + MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD; + else /* Both 'username' and 'username*' are used */ + return MHD_DIGEST_AUTH_UNAME_TYPE_INVALID; + } + else if (NULL != params->username_ext.value.str) + { + if (! params->username_ext.quoted && ! params->userhash && + (MHD_DAUTH_EXT_PARAM_MIN_LEN <= params->username_ext.value.len) ) + return MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED; + else + return MHD_DIGEST_AUTH_UNAME_TYPE_INVALID; + } + + return MHD_DIGEST_AUTH_UNAME_TYPE_MISSING; +} + + +/** + * Get total size required for 'username' and 'userhash_bin' + * @param params the Digest Authorization parameters + * @param uname_type the type of username + * @return the total size required for 'username' and + * 'userhash_bin' is userhash is used + */ +_MHD_static_inline size_t +get_rq_unames_size (const struct MHD_RqDAuth *params, + enum MHD_DigestAuthUsernameType uname_type) +{ + size_t s; + + mhd_assert (get_rq_uname_type (params) == uname_type); + s = 0; + if ((MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD == uname_type) || + (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == uname_type) ) + { + s += params->username.value.len + 1; /* Add one byte for zero-termination */ + if (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == uname_type) + s += (params->username.value.len + 1) / 2; + } + else if (MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED == uname_type) + s += params->username_ext.value.len + 1; /* Add one byte for zero-termination */ + return s; +} + + +/** + * Get client's Digest Authorization algorithm type. + * If no algorithm is specified by client, MD5 is assumed. + * @param params the Digest Authorization parameters + * @return the algorithm type + */ +static enum MHD_DigestAuthAlgo3 +get_rq_algo (const struct MHD_RqDAuth *params) +{ + const struct MHD_RqDAuthParam *const algo_param = + &params->algorithm; + if (NULL == algo_param->value.str) + return MHD_DIGEST_AUTH_ALGO3_MD5; /* Assume MD5 by default */ + + if (algo_param->quoted) + { + if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \ + algo_param->value.len, \ + _MHD_MD5_TOKEN)) + return MHD_DIGEST_AUTH_ALGO3_MD5; + if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \ + algo_param->value.len, \ + _MHD_SHA256_TOKEN)) + return MHD_DIGEST_AUTH_ALGO3_SHA256; + if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \ + algo_param->value.len, \ + _MHD_MD5_TOKEN _MHD_SESS_TOKEN)) + return MHD_DIGEST_AUTH_ALGO3_MD5_SESSION; + if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \ + algo_param->value.len, \ + _MHD_SHA256_TOKEN \ + _MHD_SESS_TOKEN)) + return MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION; + + /* Algorithms below are not supported by MHD for authentication */ + + if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \ + algo_param->value.len, \ + _MHD_SHA512_256_TOKEN)) + return MHD_DIGEST_AUTH_ALGO3_SHA512_256; + if (MHD_str_equal_caseless_quoted_s_bin_n (algo_param->value.str, \ + algo_param->value.len, \ + _MHD_SHA512_256_TOKEN \ + _MHD_SESS_TOKEN)) + return MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION; + + /* No known algorithm has been detected */ + return MHD_DIGEST_AUTH_ALGO3_INVALID; + } + /* The algorithm value is not quoted */ + if (MHD_str_equal_caseless_s_bin_n_ (_MHD_MD5_TOKEN, \ + algo_param->value.str, \ + algo_param->value.len)) + return MHD_DIGEST_AUTH_ALGO3_MD5; + if (MHD_str_equal_caseless_s_bin_n_ (_MHD_SHA256_TOKEN, \ + algo_param->value.str, \ + algo_param->value.len)) + return MHD_DIGEST_AUTH_ALGO3_MD5; + if (MHD_str_equal_caseless_s_bin_n_ (_MHD_MD5_TOKEN _MHD_SESS_TOKEN, \ + algo_param->value.str, \ + algo_param->value.len)) + return MHD_DIGEST_AUTH_ALGO3_MD5; + if (MHD_str_equal_caseless_s_bin_n_ (_MHD_SHA256_TOKEN _MHD_SESS_TOKEN, \ + algo_param->value.str, \ + algo_param->value.len)) + return MHD_DIGEST_AUTH_ALGO3_MD5; + + /* Algorithms below are not supported by MHD for authentication */ + + if (MHD_str_equal_caseless_s_bin_n_ (_MHD_SHA512_256_TOKEN, \ + algo_param->value.str, \ + algo_param->value.len)) + return MHD_DIGEST_AUTH_ALGO3_MD5; + if (MHD_str_equal_caseless_s_bin_n_ (_MHD_SHA512_256_TOKEN _MHD_SESS_TOKEN, \ + algo_param->value.str, \ + algo_param->value.len)) + return MHD_DIGEST_AUTH_ALGO3_MD5; + + /* No known algorithm has been detected */ + return MHD_DIGEST_AUTH_ALGO3_INVALID; +} + + +/** + * Get unquoted version of Digest Authorization parameter. + * This function automatically zero-teminate the result. + * @param param the parameter to extract + * @param[out] buf the output buffer, must be enough size to hold the result, + * the recommended size is 'param->value.len + 1' + * @return the size of the result, not including the terminating zero + */ +static size_t +get_rq_param_unquoted_copy_z (const struct MHD_RqDAuthParam *param, char *buf) +{ + size_t len; + mhd_assert (NULL != param->value.str); + if (! param->quoted) + { + memcpy (buf, param->value.str, param->value.len); + buf [param->value.len] = 0; + return param->value.len; + } + + len = MHD_str_unquote (param->value.str, param->value.len, buf); + mhd_assert (0 != len); + mhd_assert (len < param->value.len); + buf[len] = 0; + return len; +} + + +/** + * Get decoded version of username from extended notation. + * This function automatically zero-teminate the result. + * @param uname_ext the string of client's 'username*' parameter value + * @param uname_ext_len the length of @a uname_ext in chars + * @param[out] buf the output buffer to put decoded username value + * @param buf_size the size of @a buf + * @return the number of characters copied to the output buffer or + * -1 if wrong extended notation is used. + */ +static ssize_t +get_rq_extended_uname_copy_z (const char *uname_ext, size_t uname_ext_len, + char *buf, size_t buf_size) +{ + size_t r; + size_t w; + if ((size_t) SSIZE_MAX < uname_ext_len) + return -1; /* Too long input string */ + + if (MHD_DAUTH_EXT_PARAM_MIN_LEN > uname_ext_len) + return -1; /* Required prefix is missing */ + + if (! MHD_str_equal_caseless_bin_n_ (uname_ext, MHD_DAUTH_EXT_PARAM_PREFIX, + MHD_STATICSTR_LEN_ ( \ + MHD_DAUTH_EXT_PARAM_PREFIX))) + return -1; /* Only UTF-8 is supported, as it is implied by RFC 7616 */ + + r = MHD_STATICSTR_LEN_ (MHD_DAUTH_EXT_PARAM_PREFIX); + /* Skip language tag */ + while (r < uname_ext_len && '\'' != uname_ext[r]) + { + const char chr = uname_ext[r]; + if ((' ' == chr) || ('\t' == chr) || ('\"' == chr) || (',' == chr) || + (';' == chr) ) + return -1; /* Wrong char in language tag */ + r++; + } + if (r >= uname_ext_len) + return -1; /* The end of the language tag was not found */ + r++; /* Advance to the next char */ + + w = MHD_str_pct_decode_strict_n_ (uname_ext + r, uname_ext_len - r, + buf, buf_size); + if ((0 == w) && (0 != uname_ext_len - r)) + return -1; /* Broken percent encoding */ + buf[w] = 0; /* Zero terminate the result */ + mhd_assert (SSIZE_MAX > w); + return (ssize_t) w; +} + + +/** + * Get copy of username used by the client. + * @param params the Digest Authorization parameters + * @param uname_type the type of username + * @param[out] unames the pointer to the structure to be filled + * @param buf the buffer to be used for usernames + * @param buf_size the size of the @a buf + * @return the size of the @a buf used by pointers in @a unames structure + */ +static size_t +get_rq_uname (const struct MHD_RqDAuth *params, + enum MHD_DigestAuthUsernameType uname_type, + struct MHD_DigestAuthUsernameInfo *uname_info, + uint8_t *buf, + size_t buf_size) +{ + size_t buf_used; + + buf_used = 0; + mhd_assert (get_rq_uname_type (params) == uname_type); + mhd_assert (MHD_DIGEST_AUTH_UNAME_TYPE_INVALID != uname_type); + mhd_assert (MHD_DIGEST_AUTH_UNAME_TYPE_MISSING != uname_type); + + if ( (MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD == uname_type) || + (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == uname_type) ) + { + uname_info->username = (char *) (buf + buf_used); + uname_info->username_len = + get_rq_param_unquoted_copy_z (&params->username, + uname_info->username); + buf_used += uname_info->username_len + 1; + if (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == uname_type) + { + uname_info->userhash_bin_size = MHD_hex_to_bin (uname_info->username, + uname_info->username_len, + buf + buf_used); + if ( (0 == uname_info->userhash_bin_size) && + (0 != uname_info->username_len) ) + { + uname_info->userhash_bin = NULL; + uname_info->uname_type = MHD_DIGEST_AUTH_UNAME_TYPE_INVALID; + } + else + { + uname_info->userhash_bin = (uint8_t *) (buf + buf_used); + buf_used += uname_info->userhash_bin_size; + } + } + } + else if (MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED == uname_type) + { + ssize_t res; + res = get_rq_extended_uname_copy_z (params->username_ext.value.str, + params->username_ext.value.len, + (char *) (buf + buf_used), + buf_size - buf_used); + if (0 > res) + uname_info->uname_type = MHD_DIGEST_AUTH_UNAME_TYPE_INVALID; + else + { + uname_info->username = (char *) (buf + buf_used); + uname_info->username_len = (size_t) res; + buf_used += uname_info->username_len + 1; + } + } + mhd_assert (buf_size >= buf_used); + return buf_used; +} + + +/** + * Get QOP ('quality of protection') type. + * @param params the Digest Authorization parameters + * @return detected QOP ('quality of protection') type. + */ +static enum MHD_DigestAuthQOP +get_rq_qop (const struct MHD_RqDAuth *params) +{ + const struct MHD_RqDAuthParam *const qop_param = + &params->qop; + if (NULL == qop_param->value.str) + return MHD_DIGEST_AUTH_QOP_NONE; + if (qop_param->quoted) + { + if (MHD_str_equal_caseless_quoted_s_bin_n (qop_param->value.str, \ + qop_param->value.len, \ + "auth")) + return MHD_DIGEST_AUTH_QOP_AUTH; + if (MHD_str_equal_caseless_quoted_s_bin_n (qop_param->value.str, \ + qop_param->value.len, \ + "auth-int")) + return MHD_DIGEST_AUTH_QOP_AUTH_INT; + } + else + { + if (MHD_str_equal_caseless_s_bin_n_ ("auth", \ + qop_param->value.str, \ + qop_param->value.len)) + return MHD_DIGEST_AUTH_QOP_AUTH; + if (MHD_str_equal_caseless_s_bin_n_ ("auth-int", \ + qop_param->value.str, \ + qop_param->value.len)) + return MHD_DIGEST_AUTH_QOP_AUTH_INT; + } + /* No know QOP has been detected */ + return MHD_DIGEST_AUTH_QOP_INVALID; +} + + +/** + * Result of request's Digest Authorization 'nc' value extraction + */ +enum MHD_GetRqNCResult +{ + MHD_GET_RQ_NC_NONE = -1, /**< No 'nc' value */ + MHD_GET_RQ_NC_VALID = 0, /**< Readable 'nc' value */ + MHD_GET_RQ_NC_TOO_LONG = 1, /**< The 'nc' value is too long */ + MHD_GET_RQ_NC_TOO_LARGE = 2,/**< The 'nc' value is too big to fit uint32_t */ + MHD_GET_RQ_NC_BROKEN = 3 /**< The 'nc' value is not a number */ +}; + + +/** + * Get 'nc' value from request's Authorization header + * @param params the request digest authentication + * @param[out] nc the pointer to put nc value to + * @return enum value indicating the result + */ +static enum MHD_GetRqNCResult +get_rq_nc (const struct MHD_RqDAuth *params, + uint32_t *nc) +{ + const struct MHD_RqDAuthParam *const nc_param = + &params->nc; + char unq[16]; + const char *val; + size_t val_len; + size_t res; + uint64_t nc_val; + + if (NULL == nc_param->value.str) + return MHD_GET_RQ_NC_NONE; + + if (0 == nc_param->value.len) + return MHD_GET_RQ_NC_BROKEN; + + if (! nc_param->quoted) + { + val = nc_param->value.str; + val_len = nc_param->value.len; + } + else + { + /* Actually no backslashes must be used in 'nc' */ + if (sizeof(unq) < params->nc.value.len) + return MHD_GET_RQ_NC_TOO_LONG; + val_len = MHD_str_unquote (nc_param->value.str, nc_param->value.len, unq); + if (0 == val_len) + return MHD_GET_RQ_NC_BROKEN; + val = unq; + } + + res = MHD_strx_to_uint64_n_ (val, val_len, &nc_val); + if (0 == res) + { + const char f = val[0]; + if ( (('9' >= f) && ('0' <= f)) || + (('F' >= f) && ('A' <= f)) || + (('a' <= f) && ('f' >= f)) ) + return MHD_GET_RQ_NC_TOO_LARGE; + else + return MHD_GET_RQ_NC_BROKEN; + } + if (val_len != res) + return MHD_GET_RQ_NC_BROKEN; + if (UINT32_MAX < nc_val) + return MHD_GET_RQ_NC_TOO_LARGE; + *nc = (uint32_t) nc_val; + return MHD_GET_RQ_NC_VALID; +} + + +/** + * Get information about Digest Authorization client's header. + * + * @param connection The MHD connection structure + * @return NULL no valid Digest Authorization header is used in the request; + * a pointer structure with information if the valid request header + * found, free using #MHD_free(). + * @note Available since #MHD_VERSION 0x00097519 + * @ingroup authentication + */ +_MHD_EXTERN struct MHD_DigestAuthInfo * +MHD_digest_auth_get_request_info3 (struct MHD_Connection *connection) +{ + const struct MHD_RqDAuth *params; + struct MHD_DigestAuthInfo *info; + enum MHD_DigestAuthUsernameType uname_type; + size_t unif_buf_size; + uint8_t *unif_buf_ptr; + size_t unif_buf_used; + enum MHD_GetRqNCResult nc_res; + + params = get_rq_dauth_params (connection); + if (NULL == params) + return NULL; + + unif_buf_size = 0; + + uname_type = get_rq_uname_type (params); + + unif_buf_size += get_rq_unames_size (params, uname_type); + + if (NULL != params->opaque.value.str) + unif_buf_size += params->opaque.value.len + 1; /* Add one for zero-termination */ + if (NULL != params->realm.value.str) + unif_buf_size += params->realm.value.len + 1; /* Add one for zero-termination */ + info = (struct MHD_DigestAuthInfo *) + MHD_calloc_ (1, (sizeof(struct MHD_DigestAuthInfo)) + unif_buf_size); + unif_buf_ptr = (uint8_t *) (info + 1); + unif_buf_used = 0; + + info->algo = get_rq_algo (params); + + if ( (MHD_DIGEST_AUTH_UNAME_TYPE_MISSING != uname_type) && + (MHD_DIGEST_AUTH_UNAME_TYPE_INVALID != uname_type) ) + { + struct MHD_DigestAuthUsernameInfo uname_strct; + memset (&uname_strct, 0, sizeof(uname_strct)); + unif_buf_used += get_rq_uname (params, uname_type, &uname_strct, + unif_buf_ptr + unif_buf_used, + unif_buf_size - unif_buf_used); + info->uname_type = uname_strct.uname_type; + info->username = uname_strct.username; + info->username_len = uname_strct.username_len; + info->userhash_bin = uname_strct.userhash_bin; + info->userhash_bin_size = uname_strct.userhash_bin_size; + } + else + info->uname_type = uname_type; + + if (NULL != params->opaque.value.str) + { + info->opaque = (char *) (unif_buf_ptr + unif_buf_used); + info->opaque_len = get_rq_param_unquoted_copy_z (&params->opaque, + info->opaque); + unif_buf_used += info->opaque_len + 1; + } + if (NULL != params->realm.value.str) + { + info->realm = (char *) (unif_buf_ptr + unif_buf_used); + info->realm_len = get_rq_param_unquoted_copy_z (&params->realm, + info->realm); + unif_buf_used += info->realm_len + 1; + } + + mhd_assert (unif_buf_size >= unif_buf_used); + + info->qop = get_rq_qop (params); + + if (NULL != params->cnonce.value.str) + info->cnonce_len = params->cnonce.value.len; + else + info->cnonce_len = 0; + + nc_res = get_rq_nc (params, &info->nc); + if (MHD_GET_RQ_NC_VALID != nc_res) + info->nc = MHD_DIGEST_AUTH_INVALID_NC_VALUE; + + return info; +} + + +/** + * Get the username from Digest Authorization client's header. + * + * @param connection The MHD connection structure + * @return NULL if no valid Digest Authorization header is used in the request, + * or no username parameter is present in the header, or username is + * provided incorrectly by client (see description for + * #MHD_DIGEST_AUTH_UNAME_TYPE_INVALID); + * a pointer structure with information if the valid request header + * found, free using #MHD_free(). + * @sa MHD_digest_auth_get_request_info3() provides more complete information + * @note Available since #MHD_VERSION 0x00097519 + * @ingroup authentication + */ +_MHD_EXTERN struct MHD_DigestAuthUsernameInfo * +MHD_digest_auth_get_username3 (struct MHD_Connection *connection) +{ + const struct MHD_RqDAuth *params; + struct MHD_DigestAuthUsernameInfo *uname_info; + enum MHD_DigestAuthUsernameType uname_type; + size_t unif_buf_size; + uint8_t *unif_buf_ptr; + size_t unif_buf_used; + + params = get_rq_dauth_params (connection); + if (NULL == params) + return NULL; + + uname_type = get_rq_uname_type (params); + if ( (MHD_DIGEST_AUTH_UNAME_TYPE_MISSING == uname_type) || + (MHD_DIGEST_AUTH_UNAME_TYPE_INVALID == uname_type) ) + return NULL; + + unif_buf_size = get_rq_unames_size (params, uname_type); + + uname_info = (struct MHD_DigestAuthUsernameInfo *) + MHD_calloc_ (1, (sizeof(struct MHD_DigestAuthUsernameInfo)) + + unif_buf_size); + unif_buf_ptr = (uint8_t *) (uname_info + 1); + unif_buf_used = get_rq_uname (params, uname_type, uname_info, unif_buf_ptr, + unif_buf_size); + mhd_assert (unif_buf_size >= unif_buf_used); + (void) unif_buf_used; /* Mute compiler warning on non-debug builds */ + mhd_assert (MHD_DIGEST_AUTH_UNAME_TYPE_MISSING != uname_info->uname_type); + + if (MHD_DIGEST_AUTH_UNAME_TYPE_INVALID == uname_info->uname_type) + { + free (uname_info); + return NULL; + } + mhd_assert (uname_type == uname_info->uname_type); + + return uname_info; +} + + +/** * Get the username from the authorization header sent by the client * * @param connection The MHD connection structure