diff --git a/configure.ac b/configure.ac index 170c9aaa..7cba50af 100644 --- a/configure.ac +++ b/configure.ac @@ -2916,6 +2916,8 @@ AS_IF([[test "x$enable_dauth" != "xno"]], AM_CONDITIONAL([ENABLE_DAUTH], [test "x$enable_dauth" != "xno"]) AC_MSG_RESULT([[$enable_dauth]]) +AM_CONDITIONAL([HAVE_ANYAUTH],[test "x$enable_bauth" != "xno" || test "x$enable_dauth" != "xno"]) + # optional: HTTP "Upgrade" support. Enabled by default AC_MSG_CHECKING([[whether to support HTTP "Upgrade"]]) AC_ARG_ENABLE([[httpupgrade]], diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h index 84561dec..92a9278f 100644 --- 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 0x00097514 +#define MHD_VERSION 0x00097515 /* If generic headers don't work on your platform, include headers which define 'va_list', 'size_t', 'ssize_t', 'intptr_t', 'off_t', @@ -4368,6 +4368,8 @@ enum MHD_DigestAuthAlgorithm /** * The result of digest authentication of the client. * + * All error values are zero or negative. + * * @note Available since #MHD_VERSION 0x00097513 */ enum MHD_DigestAuthResult diff --git a/src/microhttpd/Makefile.am b/src/microhttpd/Makefile.am index 20e29c66..cf7e7f76 100644 --- a/src/microhttpd/Makefile.am +++ b/src/microhttpd/Makefile.am @@ -161,6 +161,10 @@ libmicrohttpd_la_SOURCES += \ postprocessor.c endif +if HAVE_ANYAUTH +libmicrohttpd_la_SOURCES += \ + gen_auth.c gen_auth.h +endif if ENABLE_DAUTH libmicrohttpd_la_SOURCES += \ digestauth.c digestauth.h \ diff --git a/src/microhttpd/base64.c b/src/microhttpd/base64.c index 1f07cd7e..3d2dd3f1 100644 --- a/src/microhttpd/base64.c +++ b/src/microhttpd/base64.c @@ -37,9 +37,10 @@ static const char base64_digits[] = char * -BASE64Decode (const char *src) +BASE64Decode (const char *src, + size_t in_len, + size_t *out_len) { - size_t in_len = strlen (src); unsigned char *dest; char *result; @@ -52,7 +53,7 @@ BASE64Decode (const char *src) result = (char *) dest; if (NULL == result) return NULL; /* out of memory */ - while (*src) + for (; 0 < in_len && 0 != *src; in_len -= 4) { char a = base64_digits[(unsigned char) *(src++)]; char b = base64_digits[(unsigned char) *(src++)]; @@ -75,6 +76,8 @@ BASE64Decode (const char *src) | ((unsigned char) d); } *dest = 0; + if (NULL != out_len) + *out_len = (size_t) (dest - (unsigned char *) result); return result; } diff --git a/src/microhttpd/base64.h b/src/microhttpd/base64.h index a489dc69..e5f11319 100644 --- a/src/microhttpd/base64.h +++ b/src/microhttpd/base64.h @@ -9,7 +9,18 @@ #ifndef BASE64_H #define BASE64_H +#include "mhd_options.h" +#ifdef HAVE_STDDEF_H +#include +#elif defined(HAVE_STDLIB_H) +#include +#else +#include +#endif + char * -BASE64Decode (const char *src); +BASE64Decode (const char *src, + size_t in_len, + size_t *out_len); #endif /* !BASE64_H */ diff --git a/src/microhttpd/basicauth.c b/src/microhttpd/basicauth.c index 9ef26dca..19315d14 100644 --- a/src/microhttpd/basicauth.c +++ b/src/microhttpd/basicauth.c @@ -25,6 +25,7 @@ * @author Karlson2k (Evgeny Grin) */ #include "basicauth.h" +#include "gen_auth.h" #include "platform.h" #include "mhd_limits.h" #include "internal.h" @@ -32,77 +33,103 @@ #include "mhd_compat.h" +/** + * Get request's Basic Authorisation parameters. + * @param connection the connection to process + * @return pointer to request Basic Authorisation parameters structure if + * request has such header (allocated in connection's pool), + * NULL otherwise. + */ +static const struct MHD_RqBAuth * +get_rq_bauth_params (struct MHD_Connection *connection) +{ + const struct MHD_AuthRqHeader *rq_params; + + rq_params = MHD_get_auth_rq_params_ (connection); + if ( (NULL == rq_params) || + (MHD_AUTHTYPE_BASIC != rq_params->auth_type) ) + return NULL; + + return rq_params->params.bauth; +} + + /** * Get the username and password from the basic authorization header sent by the client * * @param connection The MHD connection structure - * @param password a pointer for the password + * @param[out] password a pointer for the password, free using #MHD_free(). * @return NULL if no username could be found, a pointer - * to the username if found + * to the username if found, free using #MHD_free(). * @ingroup authentication */ _MHD_EXTERN char * MHD_basic_auth_get_username_password (struct MHD_Connection *connection, char **password) { - const char *header; + const struct MHD_RqBAuth *params; char *decode; + size_t decode_len; const char *separator; - char *user; - if ( (MHD_NO == MHD_lookup_connection_value_n (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_AUTHORIZATION, - MHD_STATICSTR_LEN_ ( - MHD_HTTP_HEADER_AUTHORIZATION), - &header, - NULL)) || - (0 != strncmp (header, - _MHD_AUTH_BASIC_BASE, - MHD_STATICSTR_LEN_ (_MHD_AUTH_BASIC_BASE))) ) + params = get_rq_bauth_params (connection); + + if (NULL == params) return NULL; - header += MHD_STATICSTR_LEN_ (_MHD_AUTH_BASIC_BASE); - if (NULL == (decode = BASE64Decode (header))) + + if ((NULL == params->token68.str) || (0 == params->token68.len)) + return NULL; + + decode = BASE64Decode (params->token68.str, params->token68.len, &decode_len); + if ((NULL == decode) || (0 == decode_len)) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("Error decoding basic authentication.\n")); #endif + if (NULL != decode) + free (decode); return NULL; } /* Find user:password pattern */ - if (NULL == (separator = strchr (decode, - ':'))) + if (NULL != (separator = memchr (decode, + ':', + decode_len))) { + char *user; + size_t user_len; + size_t password_len; + + user = decode; /* Reuse already allocated buffer */ + user_len = (size_t) (separator - decode); + user[user_len] = 0; + + if (NULL == password) + return user; + + password_len = decode_len - user_len - 1; + *password = (char *) malloc (password_len + 1); + if (NULL != *password) + { + if (0 != password_len) + memcpy (*password, decode + user_len + 1, password_len); + (*password)[password_len] = 0; + + return user; + } #ifdef HAVE_MESSAGES + else + MHD_DLOG (connection->daemon, + _ ("Failed to allocate memory.\n")); +#endif /* HAVE_MESSAGES */ + } +#ifdef HAVE_MESSAGES + else MHD_DLOG (connection->daemon, _ ("Basic authentication doesn't contain ':' separator.\n")); #endif - free (decode); - return NULL; - } - if (NULL == (user = strdup (decode))) - { - free (decode); - return NULL; - } - user[separator - decode] = '\0'; /* cut off at ':' */ - if (NULL != password) - { - *password = strdup (separator + 1); - if (NULL == *password) - { -#ifdef HAVE_MESSAGES - MHD_DLOG (connection->daemon, - _ ("Failed to allocate memory for password.\n")); -#endif - free (decode); - free (user); - return NULL; - } - } free (decode); - return user; + return NULL; } diff --git a/src/microhttpd/basicauth.h b/src/microhttpd/basicauth.h index c3ba4a4b..c46fe27a 100644 --- a/src/microhttpd/basicauth.h +++ b/src/microhttpd/basicauth.h @@ -28,10 +28,17 @@ #ifndef MHD_BASICAUTH_H #define MHD_BASICAUTH_H 1 +#include "mhd_str.h" + /** * Beginning string for any valid Basic authentication header. */ -#define _MHD_AUTH_BASIC_BASE "Basic " +#define _MHD_AUTH_BASIC_BASE "Basic" + +struct MHD_RqBAuth +{ + struct _MHD_cstr_w_len token68; +}; #endif /* ! MHD_BASICAUTH_H */ diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c index f178f44e..9977ea6b 100644 --- a/src/microhttpd/connection.c +++ b/src/microhttpd/connection.c @@ -4724,6 +4724,9 @@ connection_reset (struct MHD_Connection *connection, c->last = NULL; c->colon = NULL; c->header_size = 0; +#if defined(BAUTH_SUPPORT) || defined(DAUTH_SUPPORT) + c->rq_auth = NULL; +#endif c->keepalive = MHD_CONN_KEEPALIVE_UNKOWN; /* Reset the read buffer to the starting size, preserving the bytes we have already read. */ diff --git a/src/microhttpd/digestauth.c b/src/microhttpd/digestauth.c index 7ac5ad8b..23b41193 100644 --- a/src/microhttpd/digestauth.c +++ b/src/microhttpd/digestauth.c @@ -26,6 +26,7 @@ * @author Karlson2k (Evgeny Grin) */ #include "digestauth.h" +#include "gen_auth.h" #include "platform.h" #include "mhd_limits.h" #include "internal.h" @@ -158,19 +159,19 @@ enum MHD_CheckNonceNC_ /** * The nonce and NC are OK (valid and NC was not used before). */ - MHD_DAUTH_NONCENC_OK = MHD_DAUTH_OK, + MHD_CHECK_NONCENC_OK = MHD_DAUTH_OK, /** * The 'nonce' was overwritten with newer 'nonce' in the same slot or * NC was already used. * The validity of the 'nonce' was not be checked. */ - MHD_DAUTH_NONCENC_STALE = MHD_DAUTH_NONCE_STALE, + MHD_CHECK_NONCENC_STALE = MHD_DAUTH_NONCE_STALE, /** * The 'nonce' is wrong, it was not generated before. */ - MHD_DAUTH_NONCENC_WRONG = MHD_DAUTH_NONCE_WRONG, + MHD_CHECK_NONCENC_WRONG = MHD_DAUTH_NONCE_WRONG, }; @@ -536,7 +537,8 @@ digest_calc_ha1_from_user (const char *alg, * @param cnonce client nonce * @param qop qop-value: "", "auth" or "auth-int" (NOTE: only 'auth' is supported today.) * @param method method from request - * @param uri requested URL + * @param uri requested URL, could be not zero-terminated + * @param uri_len the length of @a uri, in characters * @param hentity H(entity body) if qop="auth-int" * @param[in,out] da digest algorithm to use, also * we write da->sessionkey (set to response request-digest or response-digest) @@ -549,6 +551,7 @@ digest_calc_response (const char *ha1, const char *qop, const char *method, const char *uri, + size_t uri_len, const char *hentity, struct DigestAlgorithm *da) { @@ -564,7 +567,7 @@ digest_calc_response (const char *ha1, 1); digest_update (da, (const unsigned char *) uri, - strlen (uri)); + uri_len); #if 0 if (0 == strcasecmp (qop, "auth-int")) @@ -627,101 +630,17 @@ digest_calc_response (const char *ha1, } -/** - * Lookup subvalue off of the HTTP Authorization header. - * - * A description of the input format for 'data' is at - * http://en.wikipedia.org/wiki/Digest_access_authentication - * - * - * @param dest where to store the result (possibly truncated if - * the buffer is not big enough). - * @param size size of dest - * @param data pointer to the Authorization header - * @param key key to look up in data - * @return size of the located value, 0 if otherwise - */ -static size_t -lookup_sub_value (char *dest, - size_t size, - const char *data, - const char *key) +static const struct MHD_RqDAuth * +get_rq_dauth_params (struct MHD_Connection *connection) { - size_t keylen; - size_t len; - const char *ptr; - const char *eq; - const char *q1; - const char *q2; - const char *qn; + const struct MHD_AuthRqHeader *rq_params; - if (0 == size) - return 0; - keylen = strlen (key); - ptr = data; - while ('\0' != *ptr) - { - if (NULL == (eq = strchr (ptr, - '='))) - return 0; - q1 = eq + 1; - while (' ' == *q1) - q1++; - if ('\"' != *q1) - { - q2 = strchr (q1, - ','); - qn = q2; - } - else - { - q1++; - q2 = strchr (q1, - '\"'); - if (NULL == q2) - return 0; /* end quote not found */ - qn = q2 + 1; - } - if ( (MHD_str_equal_caseless_n_ (ptr, - key, - keylen)) && - (eq == &ptr[keylen]) ) - { - if (NULL == q2) - { - len = strlen (q1) + 1; - if (size > len) - size = len; - size--; - memcpy (dest, - q1, - size); - dest[size] = '\0'; - return size; - } - else - { - if (size > (size_t) ((q2 - q1) + 1)) - size = (size_t) (q2 - q1) + 1; - size--; - memcpy (dest, - q1, - size); - dest[size] = '\0'; - return size; - } - } - if (NULL == qn) - return 0; - ptr = strchr (qn, - ','); - if (NULL == ptr) - return 0; - ptr++; - while (' ' == *ptr) - ptr++; - } - return 0; + rq_params = MHD_get_auth_rq_params_ (connection); + if ( (NULL == rq_params) || + (MHD_AUTHTYPE_DIGEST != rq_params->auth_type) ) + return NULL; + + return rq_params->params.dauth; } @@ -738,7 +657,6 @@ get_nonce_timestamp (const char *const nonce, size_t noncelen, uint64_t *const ptimestamp) { - mhd_assert ((0 == noncelen) || (strlen (nonce) == noncelen)); if (0 == noncelen) noncelen = strlen (nonce); @@ -826,18 +744,19 @@ check_nonce_nc (struct MHD_Connection *connection, uint32_t mod; enum MHD_CheckNonceNC_ ret; + mhd_assert (0 != noncelen); mhd_assert (strlen (nonce) == noncelen); mhd_assert (0 != nc); if (MAX_NONCE_LENGTH < noncelen) - return MHD_DAUTH_NONCENC_WRONG; /* This should be impossible, but static analysis + return MHD_CHECK_NONCENC_WRONG; /* This should be impossible, but static analysis tools have a hard time with it *and* this also protects against unsafe modifications that may happen in the future... */ mod = daemon->nonce_nc_size; if (0 == mod) - return MHD_DAUTH_NONCENC_STALE; /* no array! */ + return MHD_CHECK_NONCENC_STALE; /* no array! */ if (nc >= UINT64_MAX - 64) - return MHD_DAUTH_NONCENC_STALE; /* Overflow, unrealistically high value */ + return MHD_CHECK_NONCENC_STALE; /* Overflow, unrealistically high value */ nn = &daemon->nnc[get_nonce_nc_idx (mod, nonce, noncelen)]; @@ -851,11 +770,11 @@ check_nonce_nc (struct MHD_Connection *connection, if (0 == nn->nonce[0]) { /* The slot was never used, while the client's nonce value should be * recorded when it was generated by MHD */ - ret = MHD_DAUTH_NONCENC_WRONG; + ret = MHD_CHECK_NONCENC_WRONG; } else if (0 != nn->nonce[noncelen]) { /* The value is the slot is wrong */ - ret = MHD_DAUTH_NONCENC_STALE; + ret = MHD_CHECK_NONCENC_STALE; } else { @@ -863,7 +782,7 @@ check_nonce_nc (struct MHD_Connection *connection, if (! get_nonce_timestamp (nn->nonce, 0, &slot_ts)) { mhd_assert (0); /* The value is the slot is wrong */ - ret = MHD_DAUTH_NONCENC_STALE; + ret = MHD_CHECK_NONCENC_STALE; } else { @@ -873,21 +792,21 @@ check_nonce_nc (struct MHD_Connection *connection, { /* The nonce from the client may not have been placed in the slot * because another nonce in that slot has not yet expired. */ - ret = MHD_DAUTH_NONCENC_STALE; + ret = MHD_CHECK_NONCENC_STALE; } else if (TRIM_TO_TIMESTAMP (UINT64_MAX) / 2 >= ts_diff) { /* Too large value means that nonce_time is less than slot_ts. * The nonce from the client may have been overwritten by the newer * nonce. */ - ret = MHD_DAUTH_NONCENC_STALE; + ret = MHD_CHECK_NONCENC_STALE; } else { /* The nonce from the client should be generated after the nonce * in the slot has been expired, the nonce must be recorded, but * it's not. */ - ret = MHD_DAUTH_NONCENC_WRONG; + ret = MHD_CHECK_NONCENC_WRONG; } } } @@ -908,7 +827,7 @@ check_nonce_nc (struct MHD_Connection *connection, else nn->nmask = 0; /* big jump, unset all bits in the mask */ nn->nc = nc; - ret = MHD_DAUTH_NONCENC_OK; + ret = MHD_CHECK_NONCENC_OK; } else if (nc < nn->nc) { @@ -919,15 +838,15 @@ check_nonce_nc (struct MHD_Connection *connection, { /* Out-of-order nonce, but within 64-bit bitmask, set bit */ nn->nmask |= (UINT64_C (1) << (nn->nc - nc - 1)); - ret = MHD_DAUTH_NONCENC_OK; + ret = MHD_CHECK_NONCENC_OK; } else /* 'nc' was already used or too old (more then 64 values ago) */ - ret = MHD_DAUTH_NONCENC_STALE; + ret = MHD_CHECK_NONCENC_STALE; } else /* if (nc == nn->nc) */ /* 'nc' was already used */ - ret = MHD_DAUTH_NONCENC_STALE; + ret = MHD_CHECK_NONCENC_STALE; MHD_mutex_unlock_chk_ (&daemon->nnc_lock); @@ -947,28 +866,40 @@ check_nonce_nc (struct MHD_Connection *connection, _MHD_EXTERN char * MHD_digest_auth_get_username (struct MHD_Connection *connection) { - char user[MAX_USERNAME_LENGTH]; - const char *header; + const struct MHD_RqDAuth *params; + char *username; + size_t username_len; - if (MHD_NO == MHD_lookup_connection_value_n (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_AUTHORIZATION, - MHD_STATICSTR_LEN_ ( - MHD_HTTP_HEADER_AUTHORIZATION), - &header, - NULL)) + params = get_rq_dauth_params (connection); + if (NULL == params) return NULL; - if (0 != strncmp (header, - _MHD_AUTH_DIGEST_BASE, - MHD_STATICSTR_LEN_ (_MHD_AUTH_DIGEST_BASE))) + + if (NULL == params->username.value.str) return NULL; - header += MHD_STATICSTR_LEN_ (_MHD_AUTH_DIGEST_BASE); - if (0 == lookup_sub_value (user, - sizeof (user), - header, - "username")) + + username_len = params->username.value.len; + username = malloc (username_len + 1); + if (NULL == username) return NULL; - return strdup (user); + + if (! params->username.quoted) + { + /* The username is not quoted, no need to unquote */ + if (0 != username_len) + memcpy (username, params->username.value.str, username_len); + username[username_len] = 0; /* Zero-terminate */ + } + else + { + /* Need to properly unquote the username */ + mhd_assert (0 != username_len); /* Quoted string may not be zero-legth */ + username_len = MHD_str_unquote (params->username.value.str, username_len, + username); + mhd_assert (0 != username_len); /* The unquoted string cannot be empty */ + username[username_len] = 0; /* Zero-terminate */ + } + + return username; } @@ -1346,6 +1277,91 @@ check_argument_match (struct MHD_Connection *connection, } +/** + * The size of the unquoting buffer in stack + */ +#define _MHD_STATIC_UNQ_BUFFER_SIZE 128 + +/** + * The result of parameter unquoting + */ +enum _MHD_GetUnqResult +{ + _MHD_UNQ_NON_EMPTY = 0, /**< The sting is not empty */ + _MHD_UNQ_NO_STRING = MHD_DAUTH_WRONG_HEADER, /**< No string (no such parameter) */ + _MHD_UNQ_EMPTY = 1, /**< The string is empty */ + _MHD_UNQ_TOO_LARGE = 2, /**< The string is too large to unqoute */ + _MHD_UNQ_OUT_OF_MEM = 3 /**< Out of memory error */ +}; + + +/** + * Get Digest authorisation parameter as unquoted string. + * @param param the parameter to process + * @param tmp1 the small buffer in stack + * @param ptmp2 the pointer to pointer to malloc'ed buffer + * @param ptmp2_size the pointer to the size of the buffer pointed by @a ptmp2 + * @param[out] unquoted the pointer to store the result, NOT zero terminated + * @return enum code indicating result of the process + */ +static enum _MHD_GetUnqResult +get_unqouted_param (const struct MHD_RqDAuthParam *param, + char tmp1[_MHD_STATIC_UNQ_BUFFER_SIZE], + char **ptmp2, + size_t *ptmp2_size, + struct _MHD_cstr_w_len *unquoted) +{ + char *str; + size_t len; + mhd_assert ((0 == *ptmp2_size) || (NULL != *ptmp2)); + mhd_assert ((NULL != *ptmp2) || (0 == *ptmp2_size)); + mhd_assert ((0 == *ptmp2_size) || \ + (_MHD_STATIC_UNQ_BUFFER_SIZE < *ptmp2_size)); + + if (NULL == param->value.str) + { + const struct _MHD_cstr_w_len res = {NULL, 0}; + mhd_assert (! param->quoted); + mhd_assert (0 == param->value.len); + memcpy (unquoted, &res, sizeof(res)); + return _MHD_UNQ_NO_STRING; + } + if (! param->quoted) + { + memcpy (unquoted, ¶m->value, sizeof(param->value)); + return (0 == param->value.len) ? _MHD_UNQ_EMPTY : _MHD_UNQ_NON_EMPTY; + } + /* The value is present and is quoted, needs to be copied and unquoted */ + mhd_assert (0 != param->value.len); + if (param->value.len <= _MHD_STATIC_UNQ_BUFFER_SIZE) + str = tmp1; + else if (param->value.len <= *ptmp2_size) + str = *ptmp2; + else + { + if (param->value.len > _MHD_AUTH_DIGEST_MAX_PARAM_SIZE) + return _MHD_UNQ_TOO_LARGE; + if (NULL != *ptmp2) + free (*ptmp2); + *ptmp2 = (char *) malloc (param->value.len); + if (NULL == *ptmp2) + return _MHD_UNQ_OUT_OF_MEM; + *ptmp2_size = param->value.len; + str = *ptmp2; + } + + len = MHD_str_unquote (param->value.str, param->value.len, str); + if (1) + { + const struct _MHD_cstr_w_len res = {str, len}; + memcpy (unquoted, &res, sizeof(res)); + } + mhd_assert (0 != unquoted->len); + mhd_assert (unquoted->len < param->value.len); + return _MHD_UNQ_NON_EMPTY; +} + + /** * Authenticates the authorization header sent by the client * @@ -1375,10 +1391,6 @@ digest_auth_check_all (struct MHD_Connection *connection, unsigned int nonce_timeout) { struct MHD_Daemon *daemon = MHD_get_master (connection->daemon); - size_t len; - const char *header; - char nonce[MAX_NONCE_LENGTH]; - size_t nonce_len; char cnonce[MAX_NONCE_LENGTH]; const unsigned int digest_size = digest_get_size (da); char ha1[VLA_ARRAY_LEN_DIGEST (digest_size) * 2 + 1]; @@ -1389,219 +1401,368 @@ digest_auth_check_all (struct MHD_Connection *connection, char noncehashexp[NONCE_STD_LEN (VLA_ARRAY_LEN_DIGEST (digest_size)) + 1]; uint64_t nonce_time; uint64_t t; - size_t left; /* number of characters left in 'header' for 'uri' */ uint64_t nci; char *qmark; - - VLA_CHECK_LEN_DIGEST (digest_size); - if (MHD_NO == MHD_lookup_connection_value_n (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_AUTHORIZATION, - MHD_STATICSTR_LEN_ ( - MHD_HTTP_HEADER_AUTHORIZATION), - &header, - NULL)) - return MHD_DAUTH_WRONG_HEADER; - if (0 != strncmp (header, - _MHD_AUTH_DIGEST_BASE, - MHD_STATICSTR_LEN_ (_MHD_AUTH_DIGEST_BASE))) - return MHD_DAUTH_WRONG_HEADER; - header += MHD_STATICSTR_LEN_ (_MHD_AUTH_DIGEST_BASE); - left = strlen (header); - - if (1) - { - char un[MAX_USERNAME_LENGTH]; - - len = lookup_sub_value (un, - sizeof (un), - header, - "username"); - if (0 == len) - return MHD_DAUTH_WRONG_HEADER; - if (0 != strcmp (username, - un)) - return MHD_DAUTH_WRONG_USERNAME; - left -= strlen ("username") + len; - } - - if (1) - { - char r[MAX_REALM_LENGTH]; - - len = lookup_sub_value (r, - sizeof (r), - header, - "realm"); - if (0 == len) - return MHD_DAUTH_WRONG_HEADER; - if (0 != strcmp (realm, - r)) - return MHD_DAUTH_WRONG_REALM; - left -= strlen ("realm") + len; - } - - if (0 == (len = lookup_sub_value (nonce, - sizeof (nonce), - header, - "nonce"))) - return MHD_DAUTH_WRONG_HEADER; - nonce_len = len; - left -= strlen ("nonce") + len; - if (left > 32 * 1024) - { - /* we do not permit URIs longer than 32k, as we want to - make sure to not blow our stack (or per-connection - heap memory limit). Besides, 32k is already insanely - large, but of course in theory the - #MHD_OPTION_CONNECTION_MEMORY_LIMIT might be very large - and would thus permit sending a >32k authorization - header value. */ - return MHD_DAUTH_WRONG_HEADER; - } - if (! get_nonce_timestamp (nonce, nonce_len, &nonce_time)) - { + const struct MHD_RqDAuth *params; + char tmp1[_MHD_STATIC_UNQ_BUFFER_SIZE]; /**< Temporal buffer in stack for unqouting */ + char *tmp2; /**< Temporal malloc'ed buffer for unqouting */ + size_t tmp2_size; /**< The size of @a tmp2 buffer */ + struct _MHD_cstr_w_len unquoted; + enum _MHD_GetUnqResult unq_res; + enum MHD_DigestAuthResult ret; #ifdef HAVE_MESSAGES - MHD_DLOG (daemon, - _ ("Authentication failed, invalid timestamp format.\n")); -#endif - return MHD_DAUTH_WRONG_HEADER; - } - - t = MHD_monotonic_msec_counter (); - /* - * First level vetting for the nonce validity: if the timestamp - * attached to the nonce exceeds `nonce_timeout', then the nonce is - * invalid. - */ - if (TRIM_TO_TIMESTAMP (t - nonce_time) > (nonce_timeout * 1000)) - { - /* too old */ - return MHD_DAUTH_NONCE_STALE; - } - - calculate_nonce (nonce_time, - connection->method, - daemon->digest_auth_random, - daemon->digest_auth_rand_size, - connection->url, - realm, - da, - noncehashexp); - /* - * Second level vetting for the nonce validity - * if the timestamp attached to the nonce is valid - * and possibly fabricated (in case of an attack) - * the attacker must also know the random seed to be - * able to generate a "sane" nonce, which if he does - * not, the nonce fabrication process going to be - * very hard to achieve. - */ - if (0 != strcmp (nonce, - noncehashexp)) - { - return MHD_DAUTH_NONCE_WRONG; - } - if ( (0 == lookup_sub_value (cnonce, - sizeof (cnonce), - header, - "cnonce")) || - (0 == lookup_sub_value (qop, - sizeof (qop), - header, - "qop")) || - ( (0 != strcmp (qop, - "auth")) && - (0 != strcmp (qop, - "")) ) || - (0 == (len = lookup_sub_value (nc, - sizeof (nc), - header, - "nc")) ) || - (0 == lookup_sub_value (response, - sizeof (response), - header, - "response")) ) - { -#ifdef HAVE_MESSAGES - MHD_DLOG (daemon, - _ ("Authentication failed, invalid format.\n")); -#endif - return MHD_DAUTH_WRONG_HEADER; - } - if (len != MHD_strx_to_uint64_n_ (nc, - len, - &nci)) - { -#ifdef HAVE_MESSAGES - MHD_DLOG (daemon, - _ ("Authentication failed, invalid nc format.\n")); -#endif - return MHD_DAUTH_WRONG_HEADER; /* invalid nonce format */ - } - if (0 == nci) - { -#ifdef HAVE_MESSAGES - MHD_DLOG (daemon, - _ ("Authentication failed, invalid 'nc' value.\n")); -#endif - return MHD_DAUTH_WRONG_HEADER; /* invalid nc value */ - } - - if (1) - { - enum MHD_CheckNonceNC_ nonce_nc_check; - /* - * Checking if that combination of nonce and nc is sound - * and not a replay attack attempt. Refuse if nonce was not - * generated previously. - */ - nonce_nc_check = check_nonce_nc (connection, - nonce, - nonce_len, - nonce_time, - nci); - if (MHD_DAUTH_NONCENC_STALE == nonce_nc_check) - { -#ifdef HAVE_MESSAGES - MHD_DLOG (daemon, - _ ("Stale nonce received. If this happens a lot, you should " - "probably increase the size of the nonce array.\n")); -#endif - return MHD_DAUTH_NONCE_STALE; - } - else if (MHD_DAUTH_NONCENC_WRONG == nonce_nc_check) - { -#ifdef HAVE_MESSAGES - MHD_DLOG (daemon, - _ ("Received nonce that technically valid, but was not " - "generated by MHD. This may indicate an attack attempt.\n")); -#endif - return MHD_DAUTH_NONCE_WRONG; - } - mhd_assert (MHD_DAUTH_NONCENC_OK == nonce_nc_check); - } - - if (1) - { - char *uri; - - uri = malloc (left + 1); - if (NULL == uri) - { -#ifdef HAVE_MESSAGES - MHD_DLOG (daemon, - _ ("Failed to allocate memory for auth header processing.\n")); + bool err_logged; #endif /* HAVE_MESSAGES */ - return MHD_DAUTH_ERROR; - } - if (0 == lookup_sub_value (uri, - left + 1, - header, - "uri")) + + tmp2 = NULL; + tmp2_size = 0; +#ifdef HAVE_MESSAGES + err_logged = false; +#endif /* HAVE_MESSAGES */ + + params = get_rq_dauth_params (connection); + if (NULL == params) + return MHD_DAUTH_WRONG_HEADER; + + do /* Only to avoid "goto" */ + { + /* Check 'username' */ + unq_res = get_unqouted_param (¶ms->username, tmp1, &tmp2, &tmp2_size, + &unquoted); + if (_MHD_UNQ_NON_EMPTY != unq_res) { - free (uri); - return MHD_DAUTH_WRONG_HEADER; + if (_MHD_UNQ_NO_STRING == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_EMPTY == unq_res) + ret = MHD_DAUTH_WRONG_USERNAME; + else if (_MHD_UNQ_TOO_LARGE == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_OUT_OF_MEM == unq_res) + ret = MHD_DAUTH_ERROR; + else + { + mhd_assert (0); /* Must not happen */ + ret = MHD_DAUTH_ERROR; + } + break; + } + /* 'unquoted" may not contain binary zero */ + if ( (0 != strncmp (username, unquoted.str, unquoted.len)) || + (0 != username[unquoted.len]) ) + { + ret = MHD_DAUTH_WRONG_USERNAME; + break; + } + /* 'username' valid */ + + /* Check 'realm' */ + unq_res = get_unqouted_param (¶ms->realm, tmp1, &tmp2, &tmp2_size, + &unquoted); + if (_MHD_UNQ_NON_EMPTY != unq_res) + { + if (_MHD_UNQ_NO_STRING == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_EMPTY == unq_res) + ret = MHD_DAUTH_WRONG_REALM; + else if (_MHD_UNQ_TOO_LARGE == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_OUT_OF_MEM == unq_res) + ret = MHD_DAUTH_ERROR; + else + { + mhd_assert (0); /* Must not happen */ + ret = MHD_DAUTH_ERROR; + } + break; + } + /* 'unquoted" may not contain binary zero */ + if ( (0 != strncmp (realm, unquoted.str, unquoted.len)) || + (0 != realm[unquoted.len]) ) + { + ret = MHD_DAUTH_WRONG_REALM; + break; + } + /* 'realm' valid */ + + /* Check 'nonce' */ + unq_res = get_unqouted_param (¶ms->nonce, tmp1, &tmp2, &tmp2_size, + &unquoted); + if (_MHD_UNQ_NON_EMPTY != unq_res) + { + if (_MHD_UNQ_NO_STRING == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_EMPTY == unq_res) + ret = MHD_DAUTH_NONCE_WRONG; + else if (_MHD_UNQ_TOO_LARGE == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_OUT_OF_MEM == unq_res) + ret = MHD_DAUTH_ERROR; + else + { + mhd_assert (0); /* Must not happen */ + ret = MHD_DAUTH_ERROR; + } + break; + } + /* TODO: check correct 'nonce' length */ + if (! get_nonce_timestamp (unquoted.str, unquoted.len, &nonce_time)) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + _ ("Authentication failed, invalid timestamp format.\n")); + err_logged = true; +#endif + ret = MHD_DAUTH_NONCE_WRONG; + break; + } + t = MHD_monotonic_msec_counter (); + /* + * First level vetting for the nonce validity: if the timestamp + * attached to the nonce exceeds `nonce_timeout', then the nonce is + * invalid. + */ + if (TRIM_TO_TIMESTAMP (t - nonce_time) > (nonce_timeout * 1000)) + { + /* too old */ + ret = MHD_DAUTH_NONCE_STALE; + break; + } + + calculate_nonce (nonce_time, + connection->method, + daemon->digest_auth_random, + daemon->digest_auth_rand_size, + connection->url, + realm, + da, + noncehashexp); + /* + * Second level vetting for the nonce validity + * if the timestamp attached to the nonce is valid + * and possibly fabricated (in case of an attack) + * the attacker must also know the random seed to be + * able to generate a "sane" nonce, which if he does + * not, the nonce fabrication process going to be + * very hard to achieve. + */ + if ((0 != strncmp (noncehashexp, unquoted.str, unquoted.len)) || + (0 != noncehashexp[unquoted.len])) + { + ret = MHD_DAUTH_NONCE_WRONG; + break; + } + /* 'nonce' valid */ + + /* Get 'cnonce' */ + unq_res = get_unqouted_param (¶ms->cnonce, tmp1, &tmp2, &tmp2_size, + &unquoted); + if (_MHD_UNQ_NON_EMPTY != unq_res) + { + if (_MHD_UNQ_NO_STRING == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_EMPTY == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_TOO_LARGE == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_OUT_OF_MEM == unq_res) + ret = MHD_DAUTH_ERROR; + else + { + mhd_assert (0); /* Must not happen */ + ret = MHD_DAUTH_ERROR; + } + break; + } + if (sizeof(cnonce) <= unquoted.len) + { + /* TODO: handle large client nonces */ + ret = MHD_DAUTH_ERROR; + break; + } + /* TODO: avoid memcpy() */ + memcpy (cnonce, unquoted.str, unquoted.len); + cnonce[unquoted.len] = 0; + /* Got 'cnonce' */ + + /* Get 'qop' */ + unq_res = get_unqouted_param (¶ms->qop, tmp1, &tmp2, &tmp2_size, + &unquoted); + if (_MHD_UNQ_NON_EMPTY != unq_res) + { + if (_MHD_UNQ_NO_STRING == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_EMPTY == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_TOO_LARGE == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_OUT_OF_MEM == unq_res) + ret = MHD_DAUTH_ERROR; + else + { + mhd_assert (0); /* Must not happen */ + ret = MHD_DAUTH_ERROR; + } + break; + } + if (sizeof(qop) <= unquoted.len) + { + /* TODO: handle large client qop */ + ret = MHD_DAUTH_ERROR; + break; + } + /* TODO: avoid memcpy() */ + memcpy (qop, unquoted.str, unquoted.len); + qop[unquoted.len] = 0; + /* TODO: use caseless match, use dedicated return code */ + if ( (0 != strcmp (qop, "auth")) && + (0 != strcmp (qop,"")) ) + { + ret = MHD_DAUTH_WRONG_HEADER; + break; + } + /* Got 'qop' */ + + /* Get 'nc' */ + unq_res = get_unqouted_param (¶ms->nc, tmp1, &tmp2, &tmp2_size, + &unquoted); + if (_MHD_UNQ_NON_EMPTY != unq_res) + { + if (_MHD_UNQ_NO_STRING == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_EMPTY == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_TOO_LARGE == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_OUT_OF_MEM == unq_res) + ret = MHD_DAUTH_ERROR; + else + { + mhd_assert (0); /* Must not happen */ + ret = MHD_DAUTH_ERROR; + } + break; + } + if (sizeof(nc) <= unquoted.len) + { + /* TODO: handle large client nc */ + ret = MHD_DAUTH_ERROR; + break; + } + /* TODO: avoid memcpy() */ + memcpy (nc, unquoted.str, unquoted.len); + nc[unquoted.len] = 0; + if (unquoted.len != MHD_strx_to_uint64_n_ (nc, + unquoted.len, + &nci)) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + _ ("Authentication failed, invalid nc format.\n")); + err_logged = true; +#endif + ret = MHD_DAUTH_WRONG_HEADER; /* invalid nonce format */ + break; + } + if (0 == nci) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + _ ("Authentication failed, invalid 'nc' value.\n")); + err_logged = true; +#endif + ret = MHD_DAUTH_WRONG_HEADER; /* invalid nc value */ + break; + } + /* Got 'nc' */ + + /* Get 'response' */ + unq_res = get_unqouted_param (¶ms->response, tmp1, &tmp2, &tmp2_size, + &unquoted); + if (_MHD_UNQ_NON_EMPTY != unq_res) + { + if (_MHD_UNQ_NO_STRING == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_EMPTY == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_TOO_LARGE == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_OUT_OF_MEM == unq_res) + ret = MHD_DAUTH_ERROR; + else + { + mhd_assert (0); /* Must not happen */ + ret = MHD_DAUTH_ERROR; + } + break; + } + if (sizeof(response) <= unquoted.len) + { + /* TODO: handle large client response */ + ret = MHD_DAUTH_ERROR; + break; + } + /* TODO: avoid memcpy() */ + memcpy (response, unquoted.str, unquoted.len); + response[unquoted.len] = 0; + /* Got 'response' */ + + if (1) + { + enum MHD_CheckNonceNC_ nonce_nc_check; + /* + * Checking if that combination of nonce and nc is sound + * and not a replay attack attempt. Refuse if nonce was not + * generated previously. + */ + nonce_nc_check = check_nonce_nc (connection, + noncehashexp, + NONCE_STD_LEN (digest_size), + nonce_time, + nci); + if (MHD_CHECK_NONCENC_STALE == nonce_nc_check) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + _ ("Stale nonce received. If this happens a lot, you should " + "probably increase the size of the nonce array.\n")); + err_logged = true; +#endif + ret = MHD_DAUTH_NONCE_STALE; + break; + } + else if (MHD_CHECK_NONCENC_WRONG == nonce_nc_check) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + _ ("Received nonce that technically valid, but was not " + "generated by MHD. This may indicate an attack attempt.\n")); + err_logged = true; +#endif + ret = MHD_DAUTH_NONCE_WRONG; + break; + } + mhd_assert (MHD_CHECK_NONCENC_OK == nonce_nc_check); + } + + /* Get 'uri' */ + unq_res = get_unqouted_param (¶ms->uri, tmp1, &tmp2, &tmp2_size, + &unquoted); + if (_MHD_UNQ_NON_EMPTY != unq_res) + { + if (_MHD_UNQ_NO_STRING == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_EMPTY == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_TOO_LARGE == unq_res) + ret = MHD_DAUTH_WRONG_HEADER; + else if (_MHD_UNQ_OUT_OF_MEM == unq_res) + ret = MHD_DAUTH_ERROR; + else + { + mhd_assert (0); /* Must not happen */ + ret = MHD_DAUTH_ERROR; + } + break; } if (NULL != digest) { @@ -1609,7 +1770,7 @@ digest_auth_check_all (struct MHD_Connection *connection, digest_calc_ha1_from_digest (digest_get_algo_name (da), da, digest, - nonce, + noncehashexp, cnonce); } else @@ -1620,7 +1781,7 @@ digest_auth_check_all (struct MHD_Connection *connection, username, realm, password, - nonce, + noncehashexp, cnonce, da); } @@ -1629,60 +1790,111 @@ digest_auth_check_all (struct MHD_Connection *connection, digest_size * 2 + 1); /* This will initialize da->sessionkey (respexp) */ digest_calc_response (ha1, - nonce, + noncehashexp, nc, cnonce, qop, connection->method, - uri, + unquoted.str, + unquoted.len, hentity, da); - qmark = strchr (uri, - '?'); - if (NULL != qmark) - *qmark = '\0'; - - /* Need to unescape URI before comparing with connection->url */ - daemon->unescape_callback (daemon->unescape_callback_cls, - connection, - uri); - if (0 != strcmp (uri, - connection->url)) - { -#ifdef HAVE_MESSAGES - MHD_DLOG (daemon, - _ ("Authentication failed, URI does not match.\n")); -#endif - free (uri); - return MHD_DAUTH_WRONG_URI; - } - if (1) { - const char *args = qmark; - - if (NULL == args) - args = ""; + char *uri; + size_t uri_len; + uri_len = unquoted.len; + /* TODO: simplify string copy, avoid potential double copy */ + if ( ((tmp1 != unquoted.str) && (tmp2 != unquoted.str)) || + ((tmp1 == unquoted.str) && (sizeof(tmp1) >= unquoted.len)) || + ((tmp2 == unquoted.str) && (tmp2_size >= unquoted.len))) + { + char *buf; + mhd_assert ((tmp1 != unquoted.str) || \ + (sizeof(tmp1) == unquoted.len)); + mhd_assert ((tmp2 != unquoted.str) || \ + (tmp2_size == unquoted.len)); + buf = malloc (unquoted.len + 1); + if (NULL == buf) + { + ret = MHD_DAUTH_ERROR; + break; + } + memcpy (buf, unquoted.str, unquoted.len); + if (NULL != tmp2) + free (tmp2); + tmp2 = buf; + tmp2_size = unquoted.len + 1; + uri = tmp2; + } + else if (tmp1 == unquoted.str) + uri = tmp1; else - args++; - if (MHD_NO == - check_argument_match (connection, - args) ) + { + mhd_assert (tmp2 == unquoted.str); + uri = tmp2; + } + + uri[uri_len] = 0; + qmark = memchr (uri, + '?', + uri_len); + if (NULL != qmark) + *qmark = '\0'; + + /* Need to unescape URI before comparing with connection->url */ + daemon->unescape_callback (daemon->unescape_callback_cls, + connection, + uri); + if (0 != strcmp (uri, + connection->url)) { #ifdef HAVE_MESSAGES MHD_DLOG (daemon, - _ ("Authentication failed, arguments do not match.\n")); + _ ("Authentication failed, URI does not match.\n")); + err_logged = true; #endif - free (uri); - return MHD_DAUTH_WRONG_URI; + ret = MHD_DAUTH_WRONG_URI; + break; } + + if (1) + { + const char *args = qmark; + + if (NULL == args) + args = ""; + else + args++; + if (MHD_NO == + check_argument_match (connection, + args) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + _ ("Authentication failed, arguments do not match.\n")); + err_logged = true; +#endif + ret = MHD_DAUTH_WRONG_URI; + break; + } + } + + ret = (0 == strcmp (response, + digest_get_hex_buffer (da))) + ? MHD_DAUTH_OK + : MHD_DAUTH_RESPONSE_WRONG; } - free (uri); + } while (0); + if (NULL != tmp2) + free (tmp2); + + if ((MHD_DAUTH_OK != ret) && ! err_logged) + { + (void) 0; /* TODO: add logging */ } - return (0 == strcmp (response, - digest_get_hex_buffer (da))) - ? MHD_DAUTH_OK - : MHD_DAUTH_RESPONSE_WRONG; + + return ret; } diff --git a/src/microhttpd/digestauth.h b/src/microhttpd/digestauth.h index 689c0487..77c7c402 100644 --- a/src/microhttpd/digestauth.h +++ b/src/microhttpd/digestauth.h @@ -28,10 +28,48 @@ #ifndef MHD_DIGESTAUTH_H #define MHD_DIGESTAUTH_H 1 + +#include "mhd_options.h" +#include "mhd_str.h" +#ifdef HAVE_STDBOOL_H +#include +#endif /* HAVE_STDBOOL_H */ + + /** - * Beginning string for any valid Digest authentication header. + * The maximum supported size for Digest Auth parameters, like "real", + * "username" etc. This limitation is used only for quoted parameters. + * Parameters without quoted backslash character will be processed as long + * as they fit connection pool (buffer) size. */ -#define _MHD_AUTH_DIGEST_BASE "Digest " +#define _MHD_AUTH_DIGEST_MAX_PARAM_SIZE (65535) + +/** + * Beginning string for any valid Digest Authentication header. + */ +#define _MHD_AUTH_DIGEST_BASE "Digest" + +struct MHD_RqDAuthParam +{ + struct _MHD_cstr_w_len value; + bool quoted; +}; + +struct MHD_RqDAuth +{ + struct MHD_RqDAuthParam nonce; + struct MHD_RqDAuthParam opaque; + struct MHD_RqDAuthParam algorithm; + struct MHD_RqDAuthParam response; + struct MHD_RqDAuthParam username; + struct MHD_RqDAuthParam username_ext; + struct MHD_RqDAuthParam realm; + struct MHD_RqDAuthParam uri; + struct MHD_RqDAuthParam qop; + struct MHD_RqDAuthParam cnonce; + struct MHD_RqDAuthParam nc; + bool userhash; +}; #endif /* ! MHD_DIGESTAUTH_H */ diff --git a/src/microhttpd/gen_auth.c b/src/microhttpd/gen_auth.c new file mode 100644 index 00000000..e993fb20 --- /dev/null +++ b/src/microhttpd/gen_auth.c @@ -0,0 +1,511 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2022 Evgeny Grin (Karlson2k) + + This library 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. + + This library 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, see . +*/ + +/** + * @file microhttpd/gen_auth.c + * @brief HTTP authorisation general functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "gen_auth.h" +#include "internal.h" +#include "connection.h" +#include "mhd_str.h" +#include "mhd_assert.h" + +#ifdef BAUTH_SUPPORT +#include "basicauth.h" +#endif /* BAUTH_SUPPORT */ +#ifdef DAUTH_SUPPORT +#include "digestauth.h" +#endif /* DAUTH_SUPPORT */ + +#if ! defined(BAUTH_SUPPORT) && ! defined(DAUTH_SUPPORT) +#error This file requires Basic or Digest authentication support +#endif + +#ifdef BAUTH_SUPPORT +/** + * Parse request Authorization header parameters for Basic Authentication + * @param str the header string, everything after "Basic " substring + * @param str_len the length of @a str in characters + * @param[out] pbauth the pointer to the structure with Basic Authentication + * parameters + * @return true if parameters has been successfully parsed, + * false if format of the @a str is invalid + */ +static bool +parse_bauth_params (const char *str, + size_t str_len, + struct MHD_RqBAuth *pbauth) +{ + size_t i; + + i = 0; + + /* Skip all whitespaces at start */ + while (i < str_len && (' ' == str[i] || '\t' == str[i])) + i++; + + if (str_len > i) + { + size_t token68_start; + size_t token68_len; + + /* 'i' points to the first non-whitespace char after scheme token */ + token68_start = i; + /* Find end of the token. Token cannot contain whitespace. */ + while (i < str_len && ' ' != str[i] && '\t' != str[i]) + { + if (0 == str[0]) + return false; /* Binary zero is not allowed */ + i++; + } + token68_len = i - token68_start; + mhd_assert (0 != token68_len); + + /* Skip all whitespaces */ + while (i < str_len && (' ' == str[i] || '\t' == str[i])) + i++; + /* Check whether any garbage is present at the end of the string */ + if (str_len != i) + return false; + else + { + /* No more data in the string, only single token68. */ + const struct _MHD_cstr_w_len tkn = { str + token68_start, token68_len}; + memcpy (&pbauth->token68, &tkn, sizeof(tkn)); + } + } + return true; +} + + +#endif /* BAUTH_SUPPORT */ + +#ifdef DAUTH_SUPPORT + +/** + * Helper structure to map token name to position where to put token's value + */ +struct dauth_token_param +{ + const struct _MHD_cstr_w_len *const tk_name; + struct MHD_RqDAuthParam *const param; +}; + +/** + * Parse request Authorization header parameters for Digest Authentication + * @param str the header string, everything after "Digest " substring + * @param str_len the length of @a str in characters + * @param[out] pdauth the pointer to the structure with Digest Authentication + * parameters + * @return true if parameters has been successfully parsed, + * false if format of the @a str is invalid + */ +static bool +parse_dauth_params (const char *str, + const size_t str_len, + struct MHD_RqDAuth *pdauth) +{ + static const struct _MHD_cstr_w_len nonce_tk = _MHD_S_STR_W_LEN ("nonce"); + static const struct _MHD_cstr_w_len opaque_tk = _MHD_S_STR_W_LEN ("opaque"); + static const struct _MHD_cstr_w_len algorithm_tk = + _MHD_S_STR_W_LEN ("algorithm"); + static const struct _MHD_cstr_w_len response_tk = + _MHD_S_STR_W_LEN ("response"); + static const struct _MHD_cstr_w_len username_tk = + _MHD_S_STR_W_LEN ("username"); + static const struct _MHD_cstr_w_len username_ext_tk = + _MHD_S_STR_W_LEN ("username*"); + static const struct _MHD_cstr_w_len realm_tk = _MHD_S_STR_W_LEN ("realm"); + static const struct _MHD_cstr_w_len uri_tk = _MHD_S_STR_W_LEN ("uri"); + static const struct _MHD_cstr_w_len qop_tk = _MHD_S_STR_W_LEN ("qop"); + static const struct _MHD_cstr_w_len cnonce_tk = _MHD_S_STR_W_LEN ("cnonce"); + static const struct _MHD_cstr_w_len nc_tk = _MHD_S_STR_W_LEN ("nc"); + static const struct _MHD_cstr_w_len userhash_tk = + _MHD_S_STR_W_LEN ("userhash"); + struct MHD_RqDAuthParam userhash; + struct dauth_token_param map[] = { + {&nonce_tk, &(pdauth->nonce)}, + {&opaque_tk, &(pdauth->opaque)}, + {&algorithm_tk, &(pdauth->algorithm)}, + {&response_tk, &(pdauth->response)}, + {&username_tk, &(pdauth->username)}, + {&username_ext_tk, &(pdauth->username_ext)}, + {&realm_tk, &(pdauth->realm)}, + {&uri_tk, &(pdauth->uri)}, + {&qop_tk, &(pdauth->qop)}, + {&cnonce_tk, &(pdauth->cnonce)}, + {&nc_tk, &(pdauth->nc)}, + {&userhash_tk, &userhash} + }; + size_t i; + size_t p; + + memset (&userhash, 0, sizeof(userhash)); + i = 0; + + /* Skip all whitespaces at start */ + while (i < str_len && (' ' == str[i] || '\t' == str[i])) + i++; + + while (str_len > i) + { + size_t left; + mhd_assert (' ' != str[i]); + mhd_assert ('\t' != str[i]); + + left = str_len - i; + for (p = 0; p < sizeof(map) / sizeof(map[0]); p++) + { + struct dauth_token_param *const aparam = map + p; + if ( (aparam->tk_name->len < left) && + MHD_str_equal_caseless_bin_n_ (str + i, aparam->tk_name->str, + aparam->tk_name->len) && + (('=' == str[i + aparam->tk_name->len]) || + (' ' == str[i + aparam->tk_name->len]) || + ('\t' == str[i + aparam->tk_name->len])) ) + { + size_t value_start; + size_t value_len; + bool quoted; /* Only mark as "quoted" if backslash-escape used */ + + quoted = false; + i += aparam->tk_name->len; + /* Skip all whitespaces before '=' */ + while (str_len > i && (' ' == str[i] || '\t' == str[i])) + i++; + if ((i == str_len) || ('=' != str[i])) + return false; /* No equal sign, broken data */ + i++; + /* Skip all whitespaces after '=' */ + while (str_len > i && (' ' == str[i] || '\t' == str[i])) + i++; + if ((str_len > i) && ('"' == str[i])) + { /* Value is in quotation marks */ + i++; /* Advance after the opening quote */ + value_start = i; + while (str_len > i && '"' != str[i]) + { + if ('\\' == str[i]) + { + i++; + quoted = true; /* Have escaped chars */ + } + if (0 == str[i]) + return false; /* Binary zero in parameter value */ + i++; + } + if (str_len <= i) + return false; /* No closing quote */ + mhd_assert ('"' == str[i]); + value_len = i - value_start; + i++; /* Advance after the closing quote */ + } + else + { + value_start = i; + while (str_len > i && ',' != str[i] && + ' ' != str[i] && '\t' != str[i] && ';' != str[i]) + { + if (0 == str[i]) + return false; /* Binary zero in parameter value */ + i++; + } + value_len = i - value_start; + } + /* Skip all whitespaces after parameter value */ + while (str_len > i && (' ' == str[i] || '\t' == str[i])) + i++; + if ((str_len > i) && (',' != str[i])) + return false; /* Garbage after parameter value */ + + /* Have valid parameter name and value */ + mhd_assert (! quoted || 0 != value_len); + if (1) + { + const struct _MHD_cstr_w_len val = {str + value_start, value_len}; + memcpy (&aparam->param->value, &val, sizeof(val)); + } + aparam->param->quoted = quoted; + + break; /* Found matching parameter name */ + } + } + if (p == sizeof(map) / sizeof(map[0])) + { + /* No matching parameter name */ + while (str_len > i && ',' != str[i]) + { + if ('"' == str[i]) + { /* Skip quoted part */ + i++; /* Advance after the opening quote */ + while (str_len > i && '"' != str[i]) + { + if ('\\' == str[i]) + i++; /* Skip escaped char */ + i++; + } + if (str_len <= i) + return false; /* No closing quote */ + mhd_assert ('"' == str[i]); + } + i++; + } + } + mhd_assert (str_len == i || ',' == str[i]); + if (str_len > i) + i++; /* Advance after ',' */ + /* Skip all whitespaces before next parameter name */ + while (i < str_len && (' ' == str[i] || '\t' == str[i])) + i++; + } + + /* Postprocess values */ + if ((NULL != userhash.value.str) && (0 != userhash.value.len)) + { + const char *param_str; + size_t param_len; + char buf[5 * 2]; /* 5 is the length of "false" (longer then "true") */ + if (! userhash.quoted) + { + param_str = userhash.value.str; + param_len = userhash.value.len; + } + else + { + if (sizeof(buf) / sizeof(buf[0]) >= userhash.value.len) + { + param_len = MHD_str_unquote (userhash.value.str, userhash.value.len, + buf); + param_str = buf; + } + else + param_len = 0; + } + if ((param_len == 4) && MHD_str_equal_caseless_bin_n_ (param_str, "true", + 4)) + pdauth->userhash = true; + else + pdauth->userhash = false; + } + else + pdauth->userhash = false; + + return true; +} + + +#endif /* DAUTH_SUPPORT */ + + +/** + * Parse request "Authorization" header + * @param c the connection to process + * @return true if any supported Authorisation scheme were found, + * false if no "Authorization" header found, no supported scheme found, + * or an error occurred. + */ +_MHD_static_inline bool +parse_auth_rq_header_ (struct MHD_Connection *c) +{ + const char *h; /**< The "Authorization" header */ + size_t h_len; + struct MHD_AuthRqHeader *rq_auth; + size_t i; + + mhd_assert (NULL == c->rq_auth); + mhd_assert (MHD_CONNECTION_HEADERS_PROCESSED <= c->state); + if (MHD_CONNECTION_HEADERS_PROCESSED > c->state) + return false; + + if (MHD_NO == + MHD_lookup_connection_value_n (c, MHD_HEADER_KIND, + MHD_HTTP_HEADER_AUTHORIZATION, + MHD_STATICSTR_LEN_ ( \ + MHD_HTTP_HEADER_AUTHORIZATION), &h, + &h_len)) + { + rq_auth = (struct MHD_AuthRqHeader *) + MHD_connection_alloc_memory_ (c, + sizeof (struct MHD_AuthRqHeader)); + c->rq_auth = rq_auth; + if (NULL != rq_auth) + { + memset (rq_auth, 0, sizeof(struct MHD_AuthRqHeader)); + rq_auth->auth_type = MHD_AUTHTYPE_NONE; + } + return false; + } + + rq_auth = NULL; + i = 0; + /* Skip the leading whitespace */ + while (i < h_len) + { + const char ch = h[i]; + if ((' ' != ch) && ('\t' != ch)) + break; + i++; + } + h += i; + h_len -= i; + +#ifdef DAUTH_SUPPORT + if (1) + { + static const struct _MHD_cstr_w_len scheme_token = + _MHD_S_STR_W_LEN (_MHD_AUTH_DIGEST_BASE); + + if ((scheme_token.len <= h_len) && + MHD_str_equal_caseless_bin_n_ (h, scheme_token.str, scheme_token.len)) + { + i = scheme_token.len; + /* RFC 7235 require only space after scheme token */ + if ( (h_len <= i) || + ((' ' == h[i]) || ('\t' == h[i])) ) /* Actually tab should NOT be allowed */ + { /* Matched Digest authorisation scheme */ + i++; /* Advance to the next char (even if it is beyond the end of the string) */ + + rq_auth = (struct MHD_AuthRqHeader *) + MHD_connection_alloc_memory_ (c, + sizeof (struct MHD_AuthRqHeader) + + sizeof (struct MHD_RqDAuth)); + c->rq_auth = rq_auth; + if (NULL == rq_auth) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (c->daemon, + _ ("Failed to allocate memory in connection pool to " \ + "process \"" MHD_HTTP_HEADER_AUTHORIZATION "\" " \ + "header.\n")); +#endif /* HAVE_MESSAGES */ + return false; + } + memset (rq_auth, 0, sizeof (struct MHD_AuthRqHeader) + + sizeof (struct MHD_RqDAuth)); + rq_auth->params.dauth = (struct MHD_RqDAuth *) (rq_auth + 1); + + if (h_len > i) + { + if (! parse_dauth_params (h + i, h_len - i, rq_auth->params.dauth)) + { + rq_auth->auth_type = MHD_AUTHTYPE_INVALID; + return false; + } + } + + rq_auth->auth_type = MHD_AUTHTYPE_DIGEST; + return true; + } + } + } +#endif /* DAUTH_SUPPORT */ +#ifdef BAUTH_SUPPORT + if (1) + { + static const struct _MHD_cstr_w_len scheme_token = + _MHD_S_STR_W_LEN (_MHD_AUTH_BASIC_BASE); + + if ((scheme_token.len <= h_len) && + MHD_str_equal_caseless_bin_n_ (h, scheme_token.str, scheme_token.len)) + { + i = scheme_token.len; + /* RFC 7235 require only space after scheme token */ + if ( (h_len <= i) || + ((' ' == h[i]) || ('\t' == h[i])) ) /* Actually tab should NOT be allowed */ + { /* Matched Basic authorisation scheme */ + i++; /* Advance to the next char (even if it is beyond the end of the string) */ + + rq_auth = (struct MHD_AuthRqHeader *) + MHD_connection_alloc_memory_ (c, + sizeof (struct MHD_AuthRqHeader) + + sizeof (struct MHD_RqBAuth)); + c->rq_auth = rq_auth; + if (NULL == rq_auth) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (c->daemon, + _ ("Failed to allocate memory in connection pool to " \ + "process \"" MHD_HTTP_HEADER_AUTHORIZATION "\" " \ + "header.\n")); +#endif /* HAVE_MESSAGES */ + return false; + } + memset (rq_auth, 0, sizeof (struct MHD_AuthRqHeader) + + sizeof (struct MHD_RqBAuth)); + rq_auth->params.bauth = (struct MHD_RqBAuth *) (rq_auth + 1); + + if (h_len > i) + { + if (! parse_bauth_params (h + i, h_len - i, rq_auth->params.bauth)) + { + rq_auth->auth_type = MHD_AUTHTYPE_INVALID; + return false; + } + } + + rq_auth->auth_type = MHD_AUTHTYPE_BASIC; + return true; + } + } + } +#endif /* BAUTH_SUPPORT */ + + if (NULL == rq_auth) + rq_auth = (struct MHD_AuthRqHeader *) + MHD_connection_alloc_memory_ (c, + sizeof (struct MHD_AuthRqHeader)); + c->rq_auth = rq_auth; + if (NULL != rq_auth) + { + memset (rq_auth, 0, sizeof(struct MHD_AuthRqHeader)); + rq_auth->auth_type = MHD_AUTHTYPE_INVALID; + } + return false; +} + + +/** + * Return request's Authentication type and parameters. + * + * Function return result of parsing of the request's "Authorization" header or + * returns cached parsing result if the header was already parsed for + * the current request. + * @param connection the connection to process + * @return the pointer to structure with Authentication type and parameters, + * NULL if no memory in memory pool or if called too early (before + * header has been received). + */ +const struct MHD_AuthRqHeader * +MHD_get_auth_rq_params_ (struct MHD_Connection *connection) +{ + mhd_assert (MHD_CONNECTION_HEADERS_PROCESSED <= connection->state); + + if (NULL != connection->rq_auth) + return connection->rq_auth; + + if (MHD_CONNECTION_HEADERS_PROCESSED > connection->state) + return NULL; + + parse_auth_rq_header_ (connection); + + return connection->rq_auth; +} diff --git a/src/microhttpd/gen_auth.h b/src/microhttpd/gen_auth.h new file mode 100644 index 00000000..6be4eb4d --- /dev/null +++ b/src/microhttpd/gen_auth.h @@ -0,0 +1,93 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2022 Evgeny Grin (Karlson2k) + + This library 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. + + This library 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, see . +*/ + +/** + * @file microhttpd/gen_auth.h + * @brief Declarations for HTTP authorisation general functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_GET_AUTH_H +#define MHD_GET_AUTH_H 1 + +#include "mhd_options.h" +#ifdef HAVE_STDBOOL_H +#include +#endif /* HAVE_STDBOOL_H */ + +struct MHD_Connection; /* Forward declaration to avoid include of the large headers */ + +/** + * Type of authorisation + */ +enum MHD_AuthType +{ + MHD_AUTHTYPE_NONE = 0,/**< No authorisation */ + MHD_AUTHTYPE_BASIC, /**< Basic Authorisation, RFC 7617 */ + MHD_AUTHTYPE_DIGEST, /**< Digest Authorisation, RFC 7616 */ + MHD_AUTHTYPE_INVALID /**< Wrong/Unknown/Unsupported authorisation type */ +}; + +#ifdef BAUTH_SUPPORT +/* Forward declaration to avoid additional headers inclusion */ +struct MHD_RqBAuth; +#endif /* BAUTH_SUPPORT */ +#ifdef DAUTH_SUPPORT +/* Forward declaration to avoid additional headers inclusion */ +struct MHD_RqDAuth; +#endif /* DAUTH_SUPPORT */ + +/** + * Universal Authorisation Request parameters + */ +union MHD_AuthRqParams +{ +#ifdef BAUTH_SUPPORT + struct MHD_RqBAuth *bauth; +#endif /* BAUTH_SUPPORT */ +#ifdef DAUTH_SUPPORT + struct MHD_RqDAuth *dauth; +#endif /* DAUTH_SUPPORT */ +}; + +/** + * Request Authentication type and parameters + */ +struct MHD_AuthRqHeader +{ + enum MHD_AuthType auth_type; + union MHD_AuthRqParams params; +}; + + +/** + * Return request's Authentication type and parameters. + * + * Function return result of parsing of the request's "Authorization" header or + * returns cached parsing result if the header was already parsed for + * the current request. + * @param connection the connection to process + * @return the pointer to structure with Authentication type and parameters, + * NULL if no memory in memory pool or if called too early (before + * header has been received). + */ +const struct MHD_AuthRqHeader * +MHD_get_auth_rq_params_ (struct MHD_Connection *connection); + +#endif /* ! MHD_GET_AUTH_H */ diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h index 3dafe866..f6452b2c 100644 --- a/src/microhttpd/internal.h +++ b/src/microhttpd/internal.h @@ -778,6 +778,8 @@ typedef ssize_t size_t max_bytes); +struct MHD_AuthRqHeader; /* Forward declaration only */ + /** * Ability to use same connection for next request */ @@ -1034,6 +1036,15 @@ struct MHD_Connection */ enum MHD_HTTP_Version http_ver; +#if defined(BAUTH_SUPPORT) || defined(DAUTH_SUPPORT) + /** + * Pointer to request authorization structure. + * Allocated in pool. + */ + const struct MHD_AuthRqHeader *rq_auth; +#endif + + /** * Close connection after sending response? * Functions may change value from "Unknown" or "KeepAlive" to "Must close", diff --git a/src/testcurl/test_digestauth_concurrent.c b/src/testcurl/test_digestauth_concurrent.c index 045b673d..adfe172d 100644 --- a/src/testcurl/test_digestauth_concurrent.c +++ b/src/testcurl/test_digestauth_concurrent.c @@ -567,7 +567,7 @@ testDigestAuth (void) if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) port = 0; else - port = 1165; + port = 4200; getRnd (rnd, sizeof(rnd)); diff --git a/w32/common/libmicrohttpd-files.vcxproj b/w32/common/libmicrohttpd-files.vcxproj index 2fa2debc..391e1cec 100644 --- a/w32/common/libmicrohttpd-files.vcxproj +++ b/w32/common/libmicrohttpd-files.vcxproj @@ -6,6 +6,7 @@ + @@ -35,6 +36,7 @@ + diff --git a/w32/common/libmicrohttpd-filters.vcxproj b/w32/common/libmicrohttpd-filters.vcxproj index 684f8beb..16c6b085 100644 --- a/w32/common/libmicrohttpd-filters.vcxproj +++ b/w32/common/libmicrohttpd-filters.vcxproj @@ -52,6 +52,9 @@ Internal Headers + + Internal Headers + Internal Headers @@ -135,6 +138,9 @@ Source Files + + Source Files + Source Files