ソースを参照

Basic Auth: the first part

Evgeny Grin (Karlson2k) 1 年間 前
コミット
98c8adf03e

+ 10 - 6
configure.ac

@@ -5361,13 +5361,17 @@ AM_CONDITIONAL([USE_UPGRADE_TLS_TESTS], [[test "x${mhd_cv_gnutls_mthread_broken}
 # optional: HTTP Basic Auth support. Enabled by default
 AC_MSG_CHECKING([[whether to support HTTP Basic authentication]])
 AC_ARG_ENABLE([bauth],
-		[AS_HELP_STRING([--disable-bauth],[disable HTTP Basic Authentication support])],
-		[enable_bauth=${enableval}],
-		[enable_bauth=yes])
+  [AS_HELP_STRING([--disable-bauth],[disable HTTP Basic Authentication support])],
+  [],
+  [enable_bauth="yes"]
+)
 AS_IF([[test "x$enable_bauth" != "xno"]],
-  [ enable_bauth=yes
-    AC_DEFINE([BAUTH_SUPPORT],[1],[Define to 1 if libmicrohttpd is compiled with Basic Auth support.]) ])
-AM_CONDITIONAL([ENABLE_BAUTH], [test "x$enable_bauth" != "xno"])
+  [
+    enable_bauth="yes"
+    AC_DEFINE([MHD_ENABLE_BAUTH],[1],[Define to '1' if libmicrohttpd should be compiled with Basic Auth support.])
+  ]
+)
+AM_CONDITIONAL([MHD_ENABLE_BAUTH], [test "x$enable_bauth" = "xyes"])
 AC_MSG_RESULT([[$enable_bauth]])
 
 # optional: HTTP Digest Auth support. Enabled by default

+ 1 - 1
doc/examples/Makefile.am

@@ -25,7 +25,7 @@ noinst_PROGRAMS = \
   logging \
   responseheaders
   
-if ENABLE_BAUTH
+if MHD_ENABLE_BAUTH
 noinst_PROGRAMS += \
   basicauthentication
 if MHD_ENABLE_HTTPS

+ 44 - 19
src/include/microhttpd2.h

@@ -1437,11 +1437,21 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode
    */
   MHD_SC_RESP_HEADER_VALUE_INVALID = 60051
   ,
+  /**
+   * An attempt to add header conflicting with other response header
+   */
+  MHD_SC_RESP_HEADERS_CONFLICT = 60052
+  ,
   /**
    * The pointer to the response object is NULL
    */
   MHD_SC_RESP_POINTER_NULL = 60060
   ,
+  /**
+   * The response HTTP status code is not suitable
+   */
+  MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE = 60061
+  ,
   /**
    * The provided MHD_Action is invalid
    */
@@ -7926,34 +7936,49 @@ struct MHD_BasicAuthInfo
 };
 
 /**
- * Send a response to request basic authentication from the client.
+ * Add Basic Authentication "challenge" to the response.
  *
- * See RFC 7617, section-2 for details.
+ * The response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code.
  *
- * At most one action can be created for any request.
+ * If access to any resource should be limited to specific users, authenticated
+ * by Basic Authentication mechanism, and the request for this resource does not
+ * have Basic Authentication information (see #MHD_BasicAuthInfo), then response
+ * with Basic Authentication "challenge" should be sent. This works as
+ * an indication that Basic Authentication should be used for the access.
+ *
+ * See RFC 7617, section-2 for details.
  *
- * @param connection the MHD connection structure
- * @param realm the realm presented to the client
- * @param prefer_utf8 if not set to #MHD_NO, parameter'charset="UTF-8"' will
- *                    be added, indicating for client that UTF-8 encoding
- *                    is preferred
  * @param response the reply to send; should contain the "access denied"
  *                 body;
  *                 note: this function sets the "WWW Authenticate" header and
  *                 the caller should not set this header;
- *                 the response must have #MHD_HTTP_STATUS_FORBIDDEN status
- *                 code, must not have #MHD_R_O_REUSABLE enabled;
- *                 the NULL is tolerated (the result is NULL)
- * @return pointer to the action on success,
- *         NULL on failure
+ *                 the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status
+ *                 code;
+ *                 the NULL is tolerated (the result is
+ *                 #MHD_SC_RESP_POINTER_NULL)
+ * @param realm the realm presented to the client
+ * @param prefer_utf8 if not set to #MHD_NO, parameter'charset="UTF-8"' will
+ *                    be added, indicating for client that UTF-8 encoding
+ *                    is preferred
+ * @return #MHD_SC_OK if succeed,
+ *         #MHD_SC_TOO_LATE if the response has been already "frozen" (used to
+ *         create an action),
+ *         #MHD_SC_RESP_HEADERS_CONFLICT if Basic Authentication "challenge"
+ *         has been added already,
+ *         #MHD_SC_RESP_POINTER_NULL if @a response is NULL,
+ *         #MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE is response status code is wrong,
+ *         #MHD_SC_RESP_HEADER_VALUE_INVALID if realm is zero-length or has CR
+ *         or LF characters,
+ *         #MHD_SC_RESPONSE_HEADER_MALLOC_FAILED if memory allocation failed,
+ *         or other error code if failed
  * @ingroup authentication
  */
-MHD_EXTERN_ const struct MHD_Action *
-MHD_action_basic_auth_required_response (
-  struct MHD_Connection *connection,
-  const char *realm,
-  enum MHD_Bool prefer_utf8,
-  struct MHD_Response *response);
+MHD_EXTERN_ enum MHD_StatusCode
+MHD_response_add_basic_auth_challenge (
+  struct MHD_Response *MHD_RESTRICT response,
+  const char *MHD_RESTRICT realm,
+  enum MHD_Bool prefer_utf8)
+MHD_FN_PAR_NONNULL_(2) MHD_FN_PAR_CSTR_ (2);
 
 
 /* ********************** (f) Introspection ********************** */

+ 34 - 19
src/include/microhttpd2_main.h.in

@@ -3432,34 +3432,49 @@ struct MHD_BasicAuthInfo
 };
 
 /**
- * Send a response to request basic authentication from the client.
+ * Add Basic Authentication "challenge" to the response.
  *
- * See RFC 7617, section-2 for details.
+ * The response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code.
  *
- * At most one action can be created for any request.
+ * If access to any resource should be limited to specific users, authenticated
+ * by Basic Authentication mechanism, and the request for this resource does not
+ * have Basic Authentication information (see #MHD_BasicAuthInfo), then response
+ * with Basic Authentication "challenge" should be sent. This works as
+ * an indication that Basic Authentication should be used for the access.
+ *
+ * See RFC 7617, section-2 for details.
  *
- * @param connection the MHD connection structure
- * @param realm the realm presented to the client
- * @param prefer_utf8 if not set to #MHD_NO, parameter'charset="UTF-8"' will
- *                    be added, indicating for client that UTF-8 encoding
- *                    is preferred
  * @param response the reply to send; should contain the "access denied"
  *                 body;
  *                 note: this function sets the "WWW Authenticate" header and
  *                 the caller should not set this header;
- *                 the response must have #MHD_HTTP_STATUS_FORBIDDEN status
- *                 code, must not have #MHD_R_O_REUSABLE enabled;
- *                 the NULL is tolerated (the result is NULL)
- * @return pointer to the action on success,
- *         NULL on failure
+ *                 the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status
+ *                 code;
+ *                 the NULL is tolerated (the result is
+ *                 #MHD_SC_RESP_POINTER_NULL)
+ * @param realm the realm presented to the client
+ * @param prefer_utf8 if not set to #MHD_NO, parameter'charset="UTF-8"' will
+ *                    be added, indicating for client that UTF-8 encoding
+ *                    is preferred
+ * @return #MHD_SC_OK if succeed,
+ *         #MHD_SC_TOO_LATE if the response has been already "frozen" (used to
+ *         create an action),
+ *         #MHD_SC_RESP_HEADERS_CONFLICT if Basic Authentication "challenge"
+ *         has been added already,
+ *         #MHD_SC_RESP_POINTER_NULL if @a response is NULL,
+ *         #MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE is response status code is wrong,
+ *         #MHD_SC_RESP_HEADER_VALUE_INVALID if realm is zero-length or has CR
+ *         or LF characters,
+ *         #MHD_SC_RESPONSE_HEADER_MALLOC_FAILED if memory allocation failed,
+ *         or other error code if failed
  * @ingroup authentication
  */
-MHD_EXTERN_ const struct MHD_Action *
-MHD_action_basic_auth_required_response (
-  struct MHD_Connection *connection,
-  const char *realm,
-  enum MHD_Bool prefer_utf8,
-  struct MHD_Response *response);
+MHD_EXTERN_ enum MHD_StatusCode
+MHD_response_add_basic_auth_challenge (
+  struct MHD_Response *MHD_RESTRICT response,
+  const char *MHD_RESTRICT realm,
+  enum MHD_Bool prefer_utf8)
+MHD_FN_PAR_NONNULL_(2) MHD_FN_PAR_CSTR_ (2);
 
 
 /* ********************** (f) Introspection ********************** */

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

@@ -1437,11 +1437,21 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode
    */
   MHD_SC_RESP_HEADER_VALUE_INVALID = 60051
   ,
+  /**
+   * An attempt to add header conflicting with other response header
+   */
+  MHD_SC_RESP_HEADERS_CONFLICT = 60052
+  ,
   /**
    * The pointer to the response object is NULL
    */
   MHD_SC_RESP_POINTER_NULL = 60060
   ,
+  /**
+   * The response HTTP status code is not suitable
+   */
+  MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE = 60061
+  ,
   /**
    * The provided MHD_Action is invalid
    */

+ 7 - 0
src/mhd2/Makefile.am

@@ -102,6 +102,9 @@ post_parser_files = \
   mhd_post_parser.h         mhd_post_result.h         mhd_postfield_int.h \
   post_parser_funcs.c       post_parser_funcs.h
 
+basic_auth_sources = \
+  response_add_bauth.c
+
 upgrade_files = \
   mhd_upgrade.h \
   upgrade_prep.c            upgrade_prep.h \
@@ -130,6 +133,10 @@ if HAVE_POST_PARSER
   libmicrohttpd2_la_SOURCES += $(post_parser_files)
 endif
 
+if MHD_ENABLE_BAUTH
+  libmicrohttpd2_la_SOURCES += $(basic_auth_sources)
+endif
+
 if MHD_UPGRADE_SUPPORT
   libmicrohttpd2_la_SOURCES += $(upgrade_files)
 endif

+ 7 - 0
src/mhd2/mhd_response.h

@@ -264,6 +264,13 @@ struct mhd_ResponseConfiguration
    * Response is internal-only error response
    */
   bool int_err_resp;
+
+#ifdef MHD_ENABLE_BAUTH
+  /**
+   * Response has Basic Auth "challenge" header
+   */
+  bool has_bauth;
+#endif /* MHD_ENABLE_BAUTH */
 };
 
 /**

+ 179 - 0
src/mhd2/response_add_bauth.c

@@ -0,0 +1,179 @@
+/*
+  This file is part of GNU libmicrohttpd
+  Copyright (C) 2022-2024 Evgeny Grin (Karlson2k)
+
+  GNU libmicrohttpd is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  GNU libmicrohttpd is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+*/
+
+/**
+ * @file src/mhd2/response_add_bauth.c
+ * @brief  The definitions of MHD_response_add_basic_auth_challenge() function
+ * @author Karlson2k (Evgeny Grin)
+ */
+
+#include "mhd_sys_options.h"
+
+#include "mhd_response.h"
+#include "mhd_locks.h"
+
+#include <string.h>
+#include "sys_malloc.h"
+
+#include "mhd_str_macros.h"
+#include "mhd_str.h"
+
+#include "mhd_public_api.h"
+
+
+static MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) enum MHD_StatusCode
+response_add_basic_auth_challenge_int (struct MHD_Response *restrict response,
+                                       const char *restrict realm,
+                                       enum MHD_Bool prefer_utf8)
+{
+  static const char hdr_name[] =        MHD_HTTP_HEADER_WWW_AUTHENTICATE;
+  static const size_t hdr_name_len =    mhd_SSTR_LEN (hdr_name);
+  static const char prefix[] =          "Basic realm=\"";
+  static const size_t prefix_len =      mhd_SSTR_LEN (prefix);
+  static const char add_charset[] =     ", charset=\"UTF-8\"";
+  static const size_t add_charset_len = mhd_SSTR_LEN (add_charset);
+  const size_t realm_len = strlen (realm);
+  char *val_str;
+  size_t hval_maxlen;
+  size_t suffix_len;
+  size_t realm_quoted_len;
+  size_t pos;
+  struct mhd_ResponseHeader *new_hdr;
+
+  if ((NULL != memchr (realm, '\n', realm_len)) ||
+      (NULL != memchr (realm, '\r', realm_len)))
+    return MHD_SC_RESP_HEADER_VALUE_INVALID;
+  if (0 == realm_len)
+    return MHD_SC_RESP_HEADER_VALUE_INVALID;
+
+  suffix_len = 1; /* for (closing) quote char */
+  if (MHD_NO != prefer_utf8)
+    suffix_len += add_charset_len;
+  hval_maxlen = prefix_len + realm_len * 2 + suffix_len;
+
+  new_hdr = (struct mhd_ResponseHeader *)
+            malloc (sizeof(struct mhd_ResponseHeader)
+                    + hdr_name_len + 1 + hval_maxlen + 1);
+
+  if (NULL == new_hdr)
+    return MHD_SC_RESPONSE_HEADER_MALLOC_FAILED;
+
+  /* Set the name of the header */
+  memcpy ((char *) (new_hdr + 1),
+          hdr_name,
+          hdr_name_len + 1);
+
+  /* Set the value of the header */
+  val_str = ((char *) (new_hdr + 1)) + hdr_name_len + 1;
+  memcpy (val_str, prefix, prefix_len);
+  pos = prefix_len;
+  realm_quoted_len = mhd_str_quote (realm,
+                                    realm_len,
+                                    val_str + pos,
+                                    hval_maxlen - prefix_len - suffix_len);
+  mhd_assert (0 != realm_quoted_len);
+  pos += realm_quoted_len;
+  val_str[pos++] = '\"';
+  mhd_assert (pos + suffix_len <= hval_maxlen);
+
+  if (MHD_NO != prefer_utf8)
+  {
+    mhd_assert (pos + add_charset_len <= hval_maxlen);
+    memcpy (val_str + pos, add_charset, add_charset_len);
+    pos += add_charset_len;
+  }
+  val_str[pos] = 0; /* Zero terminate the result */
+  mhd_assert (pos <= hval_maxlen);
+
+  if (1)
+  { /* Try to shrink malloc'ed area */
+    void *new_ptr;
+    new_ptr = realloc (new_hdr,
+                       sizeof(struct mhd_ResponseHeader)
+                       + hdr_name_len + 1 + pos + 1);
+    /* Just use the old pointer if realloc() failed */
+    if (NULL != new_ptr)
+      new_hdr = (struct mhd_ResponseHeader *) new_ptr;
+  }
+
+  new_hdr->name.cstr = (char *) (new_hdr + 1);
+  new_hdr->name.len = hdr_name_len;
+  mhd_assert (0 == memcmp (hdr_name, \
+                           new_hdr->name.cstr, \
+                           new_hdr->name.len + 1));
+
+  new_hdr->value.cstr = ((char *) (new_hdr + 1)) + hdr_name_len + 1;
+  new_hdr->value.len = pos;
+  mhd_assert (0 == memcmp (prefix, \
+                           new_hdr->value.cstr, \
+                           prefix_len));
+  mhd_assert (0 == new_hdr->value.cstr[new_hdr->value.len]);
+
+  mhd_DLINKEDL_INIT_LINKS (new_hdr, headers);
+  mhd_DLINKEDL_INS_LAST (response, new_hdr, headers);
+
+  response->cfg.has_bauth = true;
+
+  return MHD_SC_OK;
+}
+
+
+MHD_EXTERN_
+MHD_FN_PAR_NONNULL_ (2)
+MHD_FN_PAR_CSTR_ (2) enum MHD_StatusCode
+MHD_response_add_basic_auth_challenge (
+  struct MHD_Response *MHD_RESTRICT response,
+  const char *realm,
+  enum MHD_Bool prefer_utf8)
+{
+  bool need_unlock;
+  enum MHD_StatusCode res;
+
+  if (NULL == response)
+    return MHD_SC_RESP_POINTER_NULL;
+  if (response->frozen)
+    return MHD_SC_TOO_LATE;
+  if (MHD_HTTP_STATUS_FORBIDDEN != response->sc)
+    return MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE;
+
+  if (response->reuse.reusable)
+  {
+    need_unlock = true;
+    if (! mhd_mutex_lock (&(response->reuse.settings_lock)))
+      return MHD_SC_RESPONSE_MUTEX_LOCK_FAILED;
+    mhd_assert (1 == mhd_atomic_counter_get (&(response->reuse.counter)));
+  }
+  else
+    need_unlock = false;
+
+  if (response->frozen) /* Re-check with the lock held */
+    res = MHD_SC_TOO_LATE;
+  else if (response->cfg.has_bauth)
+    res = MHD_SC_RESP_HEADERS_CONFLICT;
+  else
+    res = response_add_basic_auth_challenge_int (response,
+                                                 realm,
+                                                 prefer_utf8);
+
+  if (need_unlock)
+    mhd_mutex_unlock_chk (&(response->reuse.settings_lock));
+
+  return res;
+}

+ 2 - 2
src/microhttpd/Makefile.am

@@ -203,7 +203,7 @@ libmicrohttpd_la_SOURCES += \
 endif
 endif
 
-if ENABLE_BAUTH
+if MHD_ENABLE_BAUTH
 libmicrohttpd_la_SOURCES += \
   basicauth.c basicauth.h
 endif
@@ -306,7 +306,7 @@ check_PROGRAMS += \
   test_dauth_userdigest \
   test_dauth_userhash
 endif
-if ENABLE_BAUTH
+if MHD_ENABLE_BAUTH
 check_PROGRAMS += \
   test_str_base64
 endif

+ 1 - 1
src/testcurl/Makefile.am

@@ -163,7 +163,7 @@ check_PROGRAMS += \
   perf_get
 endif
 
-if ENABLE_BAUTH
+if MHD_ENABLE_BAUTH
 check_PROGRAMS += \
   test_basicauth test_basicauth_preauth \
   test_basicauth_oldapi test_basicauth_preauth_oldapi