libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

response_auth_digest.c (17629B)


      1 /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
      2 /*
      3   This file is part of GNU libmicrohttpd.
      4   Copyright (C) 2022-2024 Evgeny Grin (Karlson2k)
      5 
      6   GNU libmicrohttpd is free software; you can redistribute it and/or
      7   modify it under the terms of the GNU Lesser General Public
      8   License as published by the Free Software Foundation; either
      9   version 2.1 of the License, or (at your option) any later version.
     10 
     11   GNU libmicrohttpd is distributed in the hope that it will be useful,
     12   but WITHOUT ANY WARRANTY; without even the implied warranty of
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14   Lesser General Public License for more details.
     15 
     16   Alternatively, you can redistribute GNU libmicrohttpd and/or
     17   modify it under the terms of the GNU General Public License as
     18   published by the Free Software Foundation; either version 2 of
     19   the License, or (at your option) any later version, together
     20   with the eCos exception, as follows:
     21 
     22     As a special exception, if other files instantiate templates or
     23     use macros or inline functions from this file, or you compile this
     24     file and link it with other works to produce a work based on this
     25     file, this file does not by itself cause the resulting work to be
     26     covered by the GNU General Public License. However the source code
     27     for this file must still be made available in accordance with
     28     section (3) of the GNU General Public License v2.
     29 
     30     This exception does not invalidate any other reasons why a work
     31     based on this file might be covered by the GNU General Public
     32     License.
     33 
     34   You should have received copies of the GNU Lesser General Public
     35   License and the GNU General Public License along with this library;
     36   if not, see <https://www.gnu.org/licenses/>.
     37 */
     38 
     39 /**
     40  * @file src/mhd2/response_auth_digest.c
     41  * @brief  The definitions of MHD_response_add_auth_basic_challenge() function
     42  * @author Karlson2k (Evgeny Grin)
     43  */
     44 
     45 #include "mhd_sys_options.h"
     46 
     47 #include "sys_bool_type.h"
     48 
     49 #include "mhd_unreachable.h"
     50 #include "mhd_assert.h"
     51 
     52 #include "mhd_digest_auth_data.h"
     53 
     54 #include "mhd_response.h"
     55 #include "mhd_locks.h"
     56 #include "mhd_str_types.h"
     57 #include <string.h>
     58 #include "sys_malloc.h"
     59 
     60 #include "mhd_str_macros.h"
     61 #include "mhd_str.h"
     62 
     63 #include "mhd_public_api.h"
     64 
     65 #include "response_auth_digest.h"
     66 #include "mhd_auth_digest_hdr.h"
     67 
     68 
     69 MHD_INTERNAL
     70 MHD_FN_PAR_NONNULL_ (1) void
     71 mhd_response_remove_auth_digest_headers (struct MHD_Response*response)
     72 {
     73   struct mhd_RespAuthDigestHeader *hdr_d;
     74 
     75   for (hdr_d = mhd_DLINKEDL_GET_LAST (response, auth_d_hdrs);
     76        NULL != hdr_d;
     77        hdr_d = mhd_DLINKEDL_GET_LAST (response, auth_d_hdrs))
     78   {
     79     mhd_DLINKEDL_DEL (response, hdr_d, auth_d_hdrs);
     80     free (hdr_d);
     81   }
     82 }
     83 
     84 
     85 /**
     86  * Create and add Digest Auth challenge header with specified algorithm
     87  * @param response the response to update
     88  * @param rlm the realm to use
     89  * @param opq the "opaque" string to use
     90  * @param dmn the "domain" string to use
     91  * @param indicate_stale whether to indicate "stale" nonce
     92  * @param qop_none whether to use RFC 2069 subset only
     93  * @param algo the algorithm to use
     94  * @param userhash_support whether to indicate support for "userhash"
     95  * @param prefer_utf8 whether to indicate UTF-8 support
     96  * @return #MHD_SC_OK on success,
     97  *         error code otherwise
     98  */
     99 static MHD_FN_PAR_NONNULL_ALL_ enum MHD_StatusCode
    100 response_add_auth_digest_challenge_alg (
    101   struct MHD_Response *restrict response,
    102   const struct MHD_String *restrict rlm,
    103   const struct MHD_StringNullable *restrict opq,
    104   const struct MHD_StringNullable *restrict dmn,
    105   enum MHD_Bool indicate_stale,
    106   bool qop_none,
    107   enum MHD_DigestAuthAlgo algo,
    108   enum MHD_Bool userhash_support,
    109   enum MHD_Bool prefer_utf8)
    110 {
    111   static const struct MHD_String empty_str =
    112     mhd_MSTR_INIT ("");
    113   static const struct MHD_String hdr_pref_realm_pref =
    114     mhd_MSTR_INIT (MHD_HTTP_HEADER_WWW_AUTHENTICATE ": " \
    115                    mhd_AUTH_DIGEST_SCHEME " realm=\"");
    116   static const struct MHD_String qop_str =
    117     mhd_MSTR_INIT (", qop=\"auth\"");
    118 #ifdef MHD_SUPPORT_MD5
    119   static const struct MHD_String algo_mh5_str =
    120     mhd_MSTR_INIT (", algorithm=MD5");
    121 #endif
    122 #ifdef MHD_SUPPORT_SHA256
    123   static const struct MHD_String algo_sha256_str =
    124     mhd_MSTR_INIT (", algorithm=SHA-256");
    125 #endif
    126 #ifdef MHD_SUPPORT_SHA512_256
    127   static const struct MHD_String algo_sha512_256_str =
    128     mhd_MSTR_INIT (", algorithm=SHA-512-256");
    129 #endif
    130   static const struct MHD_String nonce_str =
    131     mhd_MSTR_INIT (", nonce=\"" \
    132                    "################################" \
    133                    "################################" \
    134                    "########" \
    135                    "\"");
    136   static size_t nonce_off = 9; /* Position of nonce value in the nonce_str */
    137   static const struct MHD_String opaque_pref =
    138     mhd_MSTR_INIT (", opaque=\"");
    139   static const struct MHD_String domain_pref =
    140     mhd_MSTR_INIT (", domain=\"");
    141   static const struct MHD_String stale_str =
    142     mhd_MSTR_INIT (", stale=true");
    143   static const struct MHD_String charset_str =
    144     mhd_MSTR_INIT (", charset=UTF-8");
    145   static const struct MHD_String userhash_str =
    146     mhd_MSTR_INIT (", userhash=true");
    147 
    148   /* Header content:
    149      + Scheme name and space;
    150      + realm, quoted;
    151      + qop="auth", quoted (optional);
    152      + algorithm, NOT quoted (optional);
    153      + nonce (placeholder), quoted;
    154      + opaque, quoted (optional);
    155      + domain, quoted (optional);
    156      + stale=true (optional);
    157      + charset=UTF-8 (optional);
    158      + userhash=true (optional).
    159    */
    160 
    161   struct MHD_String algo_str;
    162   char *hdr_str;
    163   size_t hdr_maxlen;
    164   struct mhd_RespAuthDigestHeader *new_hdr;
    165   size_t pos;
    166   size_t elm_len;
    167 
    168   mhd_assert ('#' == nonce_str.cstr[nonce_off]);
    169   mhd_assert ('#' == nonce_str.cstr[nonce_off + mhd_AUTH_DIGEST_NONCE_LEN - 1]);
    170   mhd_assert ('"' == nonce_str.cstr[nonce_off - 1]);
    171   mhd_assert ('"' == nonce_str.cstr[nonce_off + mhd_AUTH_DIGEST_NONCE_LEN]);
    172 
    173 #ifdef MHD_SUPPORT_MD5
    174   if (MHD_DIGEST_AUTH_ALGO_MD5 == algo)
    175     algo_str = qop_none ? empty_str : algo_mh5_str;
    176 #endif
    177 #ifdef MHD_SUPPORT_SHA256
    178   else if (MHD_DIGEST_AUTH_ALGO_SHA256 == algo)
    179     algo_str = algo_sha256_str;
    180 #endif
    181 #ifdef MHD_SUPPORT_SHA512_256
    182   else if (MHD_DIGEST_AUTH_ALGO_SHA512_256 == algo)
    183     algo_str = algo_sha512_256_str;
    184 #endif
    185   else
    186   {
    187     mhd_UNREACHABLE ();
    188     algo_str = empty_str; /* Mute compiler warning */
    189   }
    190 
    191   /* ** Calculate the maximum length of the header string ** */
    192   hdr_maxlen = 0;
    193 
    194   /* realm */
    195   hdr_maxlen += hdr_pref_realm_pref.len;
    196   hdr_maxlen += rlm->len * 2; /* Double length for quoting */
    197   hdr_maxlen += 1; /* closing quote (") */
    198 
    199   /* qop */
    200   hdr_maxlen += qop_str.len;
    201 
    202   /* algorithm */
    203   hdr_maxlen += algo_str.len;
    204 
    205   /* nonce */
    206   hdr_maxlen += nonce_str.len;
    207 
    208   /* opaque */
    209   hdr_maxlen += opaque_pref.len;
    210   hdr_maxlen += opq->len * 2; /* Double length for quoting */
    211   hdr_maxlen += 1; /* closing quote (") */
    212 
    213   /* domain */
    214   hdr_maxlen += domain_pref.len;
    215   hdr_maxlen += dmn->len;
    216   hdr_maxlen += 1; /* closing quote (") */
    217 
    218   /* stale */
    219   hdr_maxlen += stale_str.len;
    220 
    221   /* charset */
    222   hdr_maxlen += charset_str.len;
    223 
    224   /* userhash */
    225   hdr_maxlen += userhash_str.len;
    226 
    227   /* CRLF */
    228   hdr_maxlen += 2;
    229 
    230   /* ** Allocate ** */
    231   new_hdr = (struct mhd_RespAuthDigestHeader *)
    232             malloc (sizeof(struct mhd_RespAuthDigestHeader)
    233                     + hdr_maxlen + 1);
    234   if (NULL == new_hdr)
    235     return MHD_SC_RESPONSE_HEADER_MEM_ALLOC_FAILED;
    236   hdr_str = (char *) (new_hdr + 1);
    237 
    238   /* ** Build the header ** */
    239   pos = 0;
    240 
    241   /* realm */
    242   memcpy (hdr_str + pos,
    243           hdr_pref_realm_pref.cstr,
    244           hdr_pref_realm_pref.len);
    245   pos += hdr_pref_realm_pref.len;
    246   elm_len = mhd_str_quote (rlm->cstr,
    247                            rlm->len,
    248                            hdr_str + pos,
    249                            hdr_maxlen - pos);
    250   mhd_assert (0 != elm_len);
    251   pos += elm_len;
    252   hdr_str[pos++] = '"';
    253 
    254   /* qop */
    255   if (! qop_none)
    256   {
    257     memcpy (hdr_str + pos,
    258             qop_str.cstr,
    259             qop_str.len);
    260     pos += qop_str.len;
    261   }
    262 
    263   /* algorithm */
    264   if (0 != algo_str.len)
    265   {
    266     memcpy (hdr_str + pos,
    267             algo_str.cstr,
    268             algo_str.len);
    269     pos += algo_str.len;
    270   }
    271 
    272   /* nonce */
    273   memcpy (hdr_str + pos,
    274           nonce_str.cstr,
    275           nonce_str.len);
    276   new_hdr->nonce_pos = pos + nonce_off;
    277   pos += nonce_str.len;
    278 
    279   /* opaque */
    280   if (0 != opq->len)
    281   {
    282     memcpy (hdr_str + pos,
    283             opaque_pref.cstr,
    284             opaque_pref.len);
    285     pos += opaque_pref.len;
    286     elm_len = mhd_str_quote (opq->cstr,
    287                              opq->len,
    288                              hdr_str + pos,
    289                              hdr_maxlen - pos);
    290     mhd_assert (0 != elm_len);
    291     pos += elm_len;
    292     hdr_str[pos++] = '"';
    293   }
    294 
    295   /* domain */
    296   if (0 != dmn->len)
    297   {
    298     memcpy (hdr_str + pos,
    299             domain_pref.cstr,
    300             domain_pref.len);
    301     pos += domain_pref.len;
    302     memcpy (hdr_str + pos,
    303             dmn->cstr,
    304             dmn->len);
    305     pos += dmn->len;
    306     hdr_str[pos++] = '"';
    307   }
    308 
    309   /* stale */
    310   if (MHD_NO != indicate_stale)
    311   {
    312     memcpy (hdr_str + pos,
    313             stale_str.cstr,
    314             stale_str.len);
    315     pos += stale_str.len;
    316   }
    317 
    318   /* charset */
    319   if ((! qop_none) &&
    320       (MHD_NO != prefer_utf8))
    321   {
    322     memcpy (hdr_str + pos,
    323             charset_str.cstr,
    324             charset_str.len);
    325     pos += charset_str.len;
    326   }
    327 
    328   /* userhash */
    329   if ((! qop_none) &&
    330       (MHD_NO != userhash_support))
    331   {
    332     memcpy (hdr_str + pos,
    333             userhash_str.cstr,
    334             userhash_str.len);
    335     pos += userhash_str.len;
    336   }
    337 
    338   /* CRLF */
    339   hdr_str[pos++] = '\r';
    340   hdr_str[pos++] = '\n';
    341 
    342   mhd_assert (pos <= hdr_maxlen);
    343   hdr_str[pos] = 0; /* Zero-terminate the string */
    344 
    345   if (1)
    346   { /* Try to shrink malloc'ed area */
    347     void *new_ptr;
    348     new_ptr = realloc (new_hdr,
    349                        sizeof(struct mhd_RespAuthDigestHeader)
    350                        + pos + 1);
    351     /* Just use the old pointer if realloc() failed */
    352     if (NULL != new_ptr)
    353       new_hdr = (struct mhd_RespAuthDigestHeader *) new_ptr;
    354   }
    355 
    356   new_hdr->hdr.cstr = (char *) (new_hdr + 1);
    357   new_hdr->hdr.len = pos;
    358   mhd_assert (0 == \
    359               memcmp (new_hdr->hdr.cstr, \
    360                       MHD_HTTP_HEADER_WWW_AUTHENTICATE ": ", \
    361                       mhd_SSTR_LEN (MHD_HTTP_HEADER_WWW_AUTHENTICATE ": ")));
    362   mhd_assert (0 == new_hdr->hdr.cstr[new_hdr->hdr.len]);
    363   mhd_assert ('\r' == new_hdr->hdr.cstr[new_hdr->hdr.len - 2]);
    364   mhd_assert ('\n' == new_hdr->hdr.cstr[new_hdr->hdr.len - 1]);
    365 
    366   mhd_DLINKEDL_INIT_LINKS (new_hdr, auth_d_hdrs);
    367   mhd_DLINKEDL_INS_LAST (response, new_hdr, auth_d_hdrs);
    368 
    369   return MHD_SC_OK;
    370 }
    371 
    372 
    373 /**
    374  * Create and add Digest Auth challenge headers for all specified algorithms
    375  * @param response the response to update
    376  * @param realm the real to use
    377  * @param opaque the "opaque" string, could be NULL
    378  * @param domain the "domain" string, could be NULL
    379  * @param indicate_stale whether to indicate "stale" nonce
    380  * @param mqop the QOP values to use
    381  * @param malgo the algorithms to use
    382  * @param userhash_support whether to indicate support for "userhash"
    383  * @param prefer_utf8 whether to indicate UTF-8 support
    384  * @return #MHD_SC_OK on success,
    385  *         error code otherwise
    386  */
    387 static MHD_FN_PAR_NONNULL_ (1)
    388 MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2)
    389 MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) enum MHD_StatusCode
    390 response_add_auth_digest_challenge_int (struct MHD_Response *restrict response,
    391                                         const char *restrict realm,
    392                                         const char *restrict opaque,
    393                                         const char *restrict domain,
    394                                         enum MHD_Bool indicate_stale,
    395                                         enum MHD_DigestAuthMultiQOP mqop,
    396                                         enum MHD_DigestAuthMultiAlgo malgo,
    397                                         enum MHD_Bool userhash_support,
    398                                         enum MHD_Bool prefer_utf8)
    399 {
    400   struct MHD_String rlm;
    401   struct MHD_StringNullable opq = {0, NULL };
    402   struct MHD_StringNullable dmn = {0, NULL};
    403   const bool qop_none =
    404     (0 != (MHD_DIGEST_AUTH_QOP_NONE & ((unsigned int) mqop)));
    405   enum MHD_StatusCode res;
    406 
    407   rlm.len = strlen (realm);
    408   rlm.cstr = realm;
    409   if (NULL != opaque)
    410   {
    411     opq.len = strlen (opaque);
    412     opq.cstr = opaque;
    413   }
    414   if (NULL != domain)
    415   {
    416     dmn.len = strlen (domain);
    417     dmn.cstr = domain;
    418   }
    419 
    420   /* Check validity of the input data */
    421 
    422   if (0 == rlm.len)
    423     return MHD_SC_RESP_HEADER_VALUE_INVALID;   /* Failure exit point */
    424   if ((NULL != memchr (rlm.cstr, '\n', rlm.len)) ||
    425       (NULL != memchr (rlm.cstr, '\r', rlm.len)))
    426     return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */
    427 
    428   if ((0 != opq.len) &&
    429       ((NULL != memchr (opq.cstr, '\n', opq.len)) ||
    430        (NULL != memchr (opq.cstr, '\r', opq.len))))
    431     return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */
    432 
    433   if ((0 != dmn.len) &&
    434       ((NULL != memchr (dmn.cstr, '\n', dmn.len)) ||
    435        (NULL != memchr (dmn.cstr, '\r', dmn.len)) ||
    436        (NULL != memchr (dmn.cstr, '"', dmn.len))))
    437     return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */
    438 
    439   if (0 == (MHD_DIGEST_AUTH_ALGO_NON_SESSION & ((unsigned int) malgo)))
    440     return MHD_SC_AUTH_DIGEST_ALGO_NOT_SUPPORTED; /* Failure exit point */
    441 
    442   if (0 == ((MHD_DIGEST_AUTH_QOP_NONE | MHD_DIGEST_AUTH_QOP_AUTH)
    443             & ((unsigned int) mqop)))
    444     return MHD_SC_AUTH_DIGEST_QOP_NOT_SUPPORTED; /* Failure exit point */
    445 
    446   res = MHD_SC_OK;
    447 
    448 #ifdef MHD_SUPPORT_MD5
    449   if ((MHD_SC_OK == res) &&
    450       (0 != (MHD_DIGEST_BASE_ALGO_MD5 & ((unsigned int) malgo))))
    451     res = response_add_auth_digest_challenge_alg (response,
    452                                                   &rlm,
    453                                                   &opq,
    454                                                   &dmn,
    455                                                   indicate_stale,
    456                                                   qop_none,
    457                                                   MHD_DIGEST_AUTH_ALGO_MD5,
    458                                                   userhash_support,
    459                                                   prefer_utf8);
    460 #endif
    461 #ifdef MHD_SUPPORT_SHA256
    462   if ((MHD_SC_OK == res) &&
    463       (0 != (MHD_DIGEST_BASE_ALGO_SHA256 & ((unsigned int) malgo))))
    464     res = response_add_auth_digest_challenge_alg (response,
    465                                                   &rlm,
    466                                                   &opq,
    467                                                   &dmn,
    468                                                   indicate_stale,
    469                                                   qop_none,
    470                                                   MHD_DIGEST_AUTH_ALGO_SHA256,
    471                                                   userhash_support,
    472                                                   prefer_utf8);
    473 #endif
    474 #ifdef MHD_SUPPORT_SHA512_256
    475   if ((MHD_SC_OK == res) &&
    476       (0 != (MHD_DIGEST_BASE_ALGO_SHA512_256 & ((unsigned int) malgo))))
    477     res = response_add_auth_digest_challenge_alg (
    478       response,
    479       &rlm,
    480       &opq,
    481       &dmn,
    482       indicate_stale,
    483       qop_none,
    484       MHD_DIGEST_AUTH_ALGO_SHA512_256,
    485       userhash_support,
    486       prefer_utf8);
    487 #endif
    488 
    489   if (MHD_SC_OK != res)
    490   {
    491     mhd_response_remove_auth_digest_headers (response);
    492     return res; /* Failure exit point */
    493   }
    494 
    495   if (NULL == mhd_DLINKEDL_GET_FIRST (response, auth_d_hdrs))
    496     return MHD_SC_AUTH_DIGEST_ALGO_NOT_SUPPORTED;  /* Failure exit point */
    497 
    498   return MHD_SC_OK; /* Success exit point */
    499 }
    500 
    501 
    502 MHD_EXTERN_
    503 MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2)
    504 MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) enum MHD_StatusCode
    505 MHD_response_add_auth_digest_challenge (
    506   struct MHD_Response *MHD_RESTRICT response,
    507   const char *MHD_RESTRICT realm,
    508   const char *MHD_RESTRICT opaque,
    509   const char *MHD_RESTRICT domain,
    510   enum MHD_Bool indicate_stale,
    511   enum MHD_DigestAuthMultiQOP mqop,
    512   enum MHD_DigestAuthMultiAlgo malgo,
    513   enum MHD_Bool userhash_support,
    514   enum MHD_Bool prefer_utf8)
    515 {
    516   bool need_unlock;
    517   enum MHD_StatusCode res;
    518 
    519   if (NULL == response)
    520     return MHD_SC_RESP_POINTER_NULL;
    521   if (response->frozen)
    522     return MHD_SC_TOO_LATE;
    523   if (MHD_HTTP_STATUS_UNAUTHORIZED != response->sc)
    524     return MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE;
    525 
    526   if (response->reuse.reusable)
    527   {
    528     need_unlock = true;
    529     if (! mhd_mutex_lock (&(response->reuse.settings_lock)))
    530       return MHD_SC_RESPONSE_MUTEX_LOCK_FAILED;
    531     mhd_assert (1 == mhd_atomic_counter_get (&(response->reuse.counter)));
    532   }
    533   else
    534     need_unlock = false;
    535 
    536   if (response->frozen) /* Re-check with the lock held */
    537     res = MHD_SC_TOO_LATE;
    538   else if (NULL != mhd_DLINKEDL_GET_FIRST (response, auth_d_hdrs))
    539     res = MHD_SC_RESP_HEADERS_CONFLICT;
    540   else
    541     res = response_add_auth_digest_challenge_int (response,
    542                                                   realm,
    543                                                   opaque,
    544                                                   domain,
    545                                                   indicate_stale,
    546                                                   mqop,
    547                                                   malgo,
    548                                                   userhash_support,
    549                                                   prefer_utf8);
    550 
    551   if (need_unlock)
    552     mhd_mutex_unlock_chk (&(response->reuse.settings_lock));
    553 
    554   return res;
    555 }