blob: c10e4beea467b18ad236a2fd4f2a69f24baedaf9 [file] [log] [blame]
/*
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 <http://www.gnu.org/licenses/>.
*/
/**
* @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
/**
* Type of authorisation
*/
enum MHD_AuthType
{
MHD_AUTHTYPE_NONE = 0,/**< No authorisation, unused */
MHD_AUTHTYPE_BASIC, /**< Basic Authorisation, RFC 7617 */
MHD_AUTHTYPE_DIGEST, /**< Digest Authorisation, RFC 7616 */
MHD_AUTHTYPE_UNKNOWN, /**< Unknown/Unsupported authorisation type, unused */
MHD_AUTHTYPE_INVALID /**< Wrong/broken authorisation header, unused */
};
/**
* Find required "Authorization" request header
* @param c the connection with request
* @param type the type of the authorisation: basic or digest
* @param[out] auth_value will be set to the remaining of header value after
* authorisation token (after "Basic " or "Digest ")
* @return true if requested header is found,
* false otherwise
*/
static bool
find_auth_rq_header_ (const struct MHD_Connection *c, enum MHD_AuthType type,
struct _MHD_str_w_len *auth_value)
{
const struct MHD_HTTP_Req_Header *h;
const char *token;
size_t token_len;
mhd_assert (MHD_CONNECTION_HEADERS_PROCESSED <= c->state);
if (MHD_CONNECTION_HEADERS_PROCESSED > c->state)
return false;
#ifdef DAUTH_SUPPORT
if (MHD_AUTHTYPE_DIGEST == type)
{
token = _MHD_AUTH_DIGEST_BASE;
token_len = MHD_STATICSTR_LEN_ (_MHD_AUTH_DIGEST_BASE);
}
else /* combined with the next line */
#endif /* DAUTH_SUPPORT */
#ifdef BAUTH_SUPPORT
if (MHD_AUTHTYPE_BASIC == type)
{
token = _MHD_AUTH_BASIC_BASE;
token_len = MHD_STATICSTR_LEN_ (_MHD_AUTH_BASIC_BASE);
}
else /* combined with the next line */
#endif /* BAUTH_SUPPORT */
{
mhd_assert (0);
return false;
}
for (h = c->rq.headers_received; NULL != h; h = h->next)
{
if (MHD_HEADER_KIND != h->kind)
continue;
if (MHD_STATICSTR_LEN_ (MHD_HTTP_HEADER_AUTHORIZATION) != h->header_size)
continue;
if (token_len > h->value_size)
continue;
if (! MHD_str_equal_caseless_bin_n_ (MHD_HTTP_HEADER_AUTHORIZATION,
h->header,
MHD_STATICSTR_LEN_ ( \
MHD_HTTP_HEADER_AUTHORIZATION)))
continue;
if (! MHD_str_equal_caseless_bin_n_ (h->value, token, token_len))
continue;
/* Match only if token string is full header value or token is
* followed by space or tab
* Note: RFC 9110 (and RFC 7234) allows only space character, but
* tab is supported here as well for additional flexibility and uniformity
* as tabs are supported as separators between parameters.
*/
if ((token_len == h->value_size) ||
(' ' == h->value[token_len]) || ('\t' == h->value[token_len]))
{
if (token_len != h->value_size)
{ /* Skip whitespace */
auth_value->str = h->value + token_len + 1;
auth_value->len = h->value_size - (token_len + 1);
}
else
{ /* No whitespace to skip */
auth_value->str = h->value + token_len;
auth_value->len = h->value_size - token_len;
}
return true; /* Found a match */
}
}
return false; /* No matching header has been found */
}
#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[i])
return false; /* Binary zero is not allowed */
if ((',' == str[i]) || (';' == str[i]))
return false; /* Only single token68 is 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. */
pbauth->token68.str = str + token68_start;
pbauth->token68.len = token68_len;
}
}
return true;
}
/**
* Return request's Basic Authorisation 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 parameters,
* NULL if no memory in memory pool, if called too early (before
* header has been received) or if no valid Basic Authorisation header
* found.
*/
const struct MHD_RqBAuth *
MHD_get_rq_bauth_params_ (struct MHD_Connection *connection)
{
struct _MHD_str_w_len h_auth_value;
struct MHD_RqBAuth *bauth;
mhd_assert (MHD_CONNECTION_HEADERS_PROCESSED <= connection->state);
if (connection->rq.bauth_tried)
return connection->rq.bauth;
if (MHD_CONNECTION_HEADERS_PROCESSED > connection->state)
return NULL;
if (! find_auth_rq_header_ (connection, MHD_AUTHTYPE_BASIC, &h_auth_value))
{
connection->rq.bauth_tried = true;
connection->rq.bauth = NULL;
return NULL;
}
bauth =
(struct MHD_RqBAuth *)
MHD_connection_alloc_memory_ (connection, sizeof (struct MHD_RqBAuth));
if (NULL == bauth)
{
#ifdef HAVE_MESSAGES
MHD_DLOG (connection->daemon,
_ ("Not enough memory in the connection's pool to allocate " \
"for Basic Authorization header parsing.\n"));
#endif /* HAVE_MESSAGES */
return NULL;
}
memset (bauth, 0, sizeof(struct MHD_RqBAuth));
if (parse_bauth_params (h_auth_value.str, h_auth_value.len, bauth))
connection->rq.bauth = bauth;
else
{
#ifdef HAVE_MESSAGES
MHD_DLOG (connection->daemon,
_ ("The Basic Authorization client's header has "
"incorrect format.\n"));
#endif /* HAVE_MESSAGES */
connection->rq.bauth = NULL;
/* Memory in the pool remains allocated until next request */
}
connection->rq.bauth_tried = true;
return connection->rq.bauth;
}
#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;
};
/**
* Get client's Digest Authorization algorithm type.
* If no algorithm is specified by client, MD5 is assumed.
* @param params the Digest Authorization 'algorithm' parameter
* @return the algorithm type
*/
static enum MHD_DigestAuthAlgo3
get_rq_dauth_algo (const struct MHD_RqDAuthParam *const algo_param)
{
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_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))
/* Algorithms below are not supported by MHD for authentication */
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;
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_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_SHA256;
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_SHA512_256;
/* Algorithms below are not supported by MHD for authentication */
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_SESSION;
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_SHA256_SESSION;
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_SHA512_256_SESSION;
/* No known algorithm has been detected */
return MHD_DIGEST_AUTH_ALGO3_INVALID;
}
/**
* Get QOP ('quality of protection') type.
* @param qop_param the Digest Authorization 'QOP' parameter
* @return detected QOP ('quality of protection') type.
*/
static enum MHD_DigestAuthQOP
get_rq_dauth_qop (const struct MHD_RqDAuthParam *const qop_param)
{
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, \
MHD_TOKEN_AUTH_))
return MHD_DIGEST_AUTH_QOP_AUTH;
if (MHD_str_equal_caseless_quoted_s_bin_n (qop_param->value.str, \
qop_param->value.len, \
MHD_TOKEN_AUTH_INT_))
return MHD_DIGEST_AUTH_QOP_AUTH_INT;
}
else
{
if (MHD_str_equal_caseless_s_bin_n_ (MHD_TOKEN_AUTH_, \
qop_param->value.str, \
qop_param->value.len))
return MHD_DIGEST_AUTH_QOP_AUTH;
if (MHD_str_equal_caseless_s_bin_n_ (MHD_TOKEN_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;
}
/**
* 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 MHD_RqDAuthParam algorithm;
struct dauth_token_param map[] = {
{&nonce_tk, &(pdauth->nonce)},
{&opaque_tk, &(pdauth->opaque)},
{&algorithm_tk, &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_raw)},
{&cnonce_tk, &(pdauth->cnonce)},
{&nc_tk, &(pdauth->nc)},
{&userhash_tk, &userhash}
};
size_t i;
size_t p;
memset (&userhash, 0, sizeof(userhash));
memset (&algorithm, 0, sizeof(algorithm));
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;
if ('=' == str[i])
return false; /* The equal sign is not allowed as the first character */
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) &&
((aparam->tk_name->len == left) ||
('=' == str[i + aparam->tk_name->len]) ||
(' ' == str[i + aparam->tk_name->len]) ||
('\t' == str[i + aparam->tk_name->len]) ||
(',' == str[i + aparam->tk_name->len]) ||
(';' == str[i + aparam->tk_name->len])) )
{
size_t value_start;
size_t value_len;
bool quoted; /* Only mark as "quoted" if backslash-escape used */
if (aparam->tk_name->len == left)
return false; /* No equal sign after parameter name, broken data */
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++;
}
if (';' == str[i])
return false; /* Semicolon in parameter value */
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);
aparam->param->value.str = str + value_start;
aparam->param->value.len = value_len;
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 ((0 == str[i]) || (';' == str[i]))
return false; /* Not allowed characters */
if ('"' == str[i])
{ /* Skip quoted part */
i++; /* Advance after the opening quote */
while (str_len > i && '"' != str[i])
{
if (0 == str[i])
return false; /* Binary zero is not allowed */
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)
{
if (userhash.quoted)
pdauth->userhash =
MHD_str_equal_caseless_quoted_s_bin_n (userhash.value.str, \
userhash.value.len, \
"true");
else
pdauth->userhash =
MHD_str_equal_caseless_s_bin_n_ ("true", userhash.value.str, \
userhash.value.len);
}
else
pdauth->userhash = false;
pdauth->algo3 = get_rq_dauth_algo (&algorithm);
pdauth->qop = get_rq_dauth_qop (&pdauth->qop_raw);
return true;
}
/**
* Return request's Digest Authorisation 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 parameters,
* NULL if no memory in memory pool, if called too early (before
* header has been received) or if no valid Basic Authorisation header
* found.
*/
const struct MHD_RqDAuth *
MHD_get_rq_dauth_params_ (struct MHD_Connection *connection)
{
struct _MHD_str_w_len h_auth_value;
struct MHD_RqDAuth *dauth;
mhd_assert (MHD_CONNECTION_HEADERS_PROCESSED <= connection->state);
if (connection->rq.dauth_tried)
return connection->rq.dauth;
if (MHD_CONNECTION_HEADERS_PROCESSED > connection->state)
return NULL;
if (! find_auth_rq_header_ (connection, MHD_AUTHTYPE_DIGEST, &h_auth_value))
{
connection->rq.dauth_tried = true;
connection->rq.dauth = NULL;
return NULL;
}
dauth =
(struct MHD_RqDAuth *)
MHD_connection_alloc_memory_ (connection, sizeof (struct MHD_RqDAuth));
if (NULL == dauth)
{
#ifdef HAVE_MESSAGES
MHD_DLOG (connection->daemon,
_ ("Not enough memory in the connection's pool to allocate " \
"for Digest Authorization header parsing.\n"));
#endif /* HAVE_MESSAGES */
return NULL;
}
memset (dauth, 0, sizeof(struct MHD_RqDAuth));
if (parse_dauth_params (h_auth_value.str, h_auth_value.len, dauth))
connection->rq.dauth = dauth;
else
{
#ifdef HAVE_MESSAGES
MHD_DLOG (connection->daemon,
_ ("The Digest Authorization client's header has "
"incorrect format.\n"));
#endif /* HAVE_MESSAGES */
connection->rq.dauth = NULL;
/* Memory in the pool remains allocated until next request */
}
connection->rq.dauth_tried = true;
return connection->rq.dauth;
}
#endif /* DAUTH_SUPPORT */