C++程序  |  872行  |  22.29 KB

/*
     This file is part of libmicrohttpd
     Copyright (C) 2010, 2011, 2012 Daniel Pittman and Christian Grothoff

     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, write to the Free Software
     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
/**
 * @file digestauth.c
 * @brief Implements HTTP digest authentication
 * @author Amr Ali
 * @author Matthieu Speder
 */
#include "platform.h"
#include <limits.h>
#include "internal.h"
#include "md5.h"

#if defined(_WIN32) && defined(MHD_W32_MUTEX_)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif /* !WIN32_LEAN_AND_MEAN */
#include <windows.h>
#endif /* _WIN32 && MHD_W32_MUTEX_ */

#define HASH_MD5_HEX_LEN (2 * MD5_DIGEST_SIZE)

/**
 * Beginning string for any valid Digest authentication header.
 */
#define _BASE		"Digest "

/**
 * Maximum length of a username for digest authentication.
 */
#define MAX_USERNAME_LENGTH 128

/**
 * Maximum length of a realm for digest authentication.
 */
#define MAX_REALM_LENGTH 256

/**
 * Maximum length of the response in digest authentication.
 */
#define MAX_AUTH_RESPONSE_LENGTH 128


/**
 * convert bin to hex
 *
 * @param bin binary data
 * @param len number of bytes in bin
 * @param hex pointer to len*2+1 bytes
 */
static void
cvthex (const unsigned char *bin,
	size_t len,
	char *hex)
{
  size_t i;
  unsigned int j;

  for (i = 0; i < len; ++i)
    {
      j = (bin[i] >> 4) & 0x0f;
      hex[i * 2] = j <= 9 ? (j + '0') : (j + 'a' - 10);
      j = bin[i] & 0x0f;
      hex[i * 2 + 1] = j <= 9 ? (j + '0') : (j + 'a' - 10);
    }
  hex[len * 2] = '\0';
}


/**
 * calculate H(A1) as per RFC2617 spec and store the
 * result in 'sessionkey'.
 *
 * @param alg The hash algorithm used, can be "md5" or "md5-sess"
 * @param username A `char *' pointer to the username value
 * @param realm A `char *' pointer to the realm value
 * @param password A `char *' pointer to the password value
 * @param nonce A `char *' pointer to the nonce value
 * @param cnonce A `char *' pointer to the cnonce value
 * @param sessionkey pointer to buffer of HASH_MD5_HEX_LEN+1 bytes
 */
static void
digest_calc_ha1 (const char *alg,
		 const char *username,
		 const char *realm,
		 const char *password,
		 const char *nonce,
		 const char *cnonce,
		 char *sessionkey)
{
  struct MD5Context md5;
  unsigned char ha1[MD5_DIGEST_SIZE];

  MD5Init (&md5);
  MD5Update (&md5, username, strlen (username));
  MD5Update (&md5, ":", 1);
  MD5Update (&md5, realm, strlen (realm));
  MD5Update (&md5, ":", 1);
  MD5Update (&md5, password, strlen (password));
  MD5Final (ha1, &md5);
  if (MHD_str_equal_caseless_(alg, "md5-sess"))
    {
      MD5Init (&md5);
      MD5Update (&md5, ha1, sizeof (ha1));
      MD5Update (&md5, ":", 1);
      MD5Update (&md5, nonce, strlen (nonce));
      MD5Update (&md5, ":", 1);
      MD5Update (&md5, cnonce, strlen (cnonce));
      MD5Final (ha1, &md5);
    }
  cvthex (ha1, sizeof (ha1), sessionkey);
}


/**
 * Calculate request-digest/response-digest as per RFC2617 spec
 *
 * @param ha1 H(A1)
 * @param nonce nonce from server
 * @param noncecount 8 hex digits
 * @param cnonce client nonce
 * @param qop qop-value: "", "auth" or "auth-int"
 * @param method method from request
 * @param uri requested URL
 * @param hentity H(entity body) if qop="auth-int"
 * @param response request-digest or response-digest
 */
static void
digest_calc_response (const char *ha1,
		      const char *nonce,
		      const char *noncecount,
		      const char *cnonce,
		      const char *qop,
		      const char *method,
		      const char *uri,
		      const char *hentity,
		      char *response)
{
  struct MD5Context md5;
  unsigned char ha2[MD5_DIGEST_SIZE];
  unsigned char resphash[MD5_DIGEST_SIZE];
  char ha2hex[HASH_MD5_HEX_LEN + 1];

  MD5Init (&md5);
  MD5Update (&md5, method, strlen(method));
  MD5Update (&md5, ":", 1);
  MD5Update (&md5, uri, strlen(uri));
#if 0
  if (0 == strcasecmp(qop, "auth-int"))
    {
      /* This is dead code since the rest of this module does
	 not support auth-int. */
      MD5Update (&md5, ":", 1);
      if (NULL != hentity)
	MD5Update (&md5, hentity, strlen(hentity));
    }
#endif
  MD5Final (ha2, &md5);
  cvthex (ha2, MD5_DIGEST_SIZE, ha2hex);
  MD5Init (&md5);
  /* calculate response */
  MD5Update (&md5, ha1, HASH_MD5_HEX_LEN);
  MD5Update (&md5, ":", 1);
  MD5Update (&md5, nonce, strlen(nonce));
  MD5Update (&md5, ":", 1);
  if ('\0' != *qop)
    {
      MD5Update (&md5, noncecount, strlen(noncecount));
      MD5Update (&md5, ":", 1);
      MD5Update (&md5, cnonce, strlen(cnonce));
      MD5Update (&md5, ":", 1);
      MD5Update (&md5, qop, strlen(qop));
      MD5Update (&md5, ":", 1);
    }
  MD5Update (&md5, ha2hex, HASH_MD5_HEX_LEN);
  MD5Final (resphash, &md5);
  cvthex (resphash, sizeof (resphash), response);
}


/**
 * 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)
{
  size_t keylen;
  size_t len;
  const char *ptr;
  const char *eq;
  const char *q1;
  const char *q2;
  const char *qn;

  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--;
	      strncpy (dest,
		       q1,
		       size);
	      dest[size] = '\0';
	      return size;
	    }
	  else
	    {
	      if (size > (size_t) ((q2 - q1) + 1))
		size = (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;
}


/**
 * Check nonce-nc map array with either new nonce counter
 * or a whole new nonce.
 *
 * @param connection The MHD connection structure
 * @param nonce A pointer that referenced a zero-terminated array of nonce
 * @param nc The nonce counter, zero to add the nonce to the array
 * @return MHD_YES if successful, MHD_NO if invalid (or we have no NC array)
 */
static int
check_nonce_nc (struct MHD_Connection *connection,
		const char *nonce,
		unsigned long int nc)
{
  uint32_t off;
  uint32_t mod;
  const char *np;

  mod = connection->daemon->nonce_nc_size;
  if (0 == mod)
    return MHD_NO; /* no array! */
  /* super-fast xor-based "hash" function for HT lookup in nonce array */
  off = 0;
  np = nonce;
  while ('\0' != *np)
    {
      off = (off << 8) | (*np ^ (off >> 24));
      np++;
    }
  off = off % mod;
  /*
   * Look for the nonce, if it does exist and its corresponding
   * nonce counter is less than the current nonce counter by 1,
   * then only increase the nonce counter by one.
   */

  (void) MHD_mutex_lock_ (&connection->daemon->nnc_lock);
  if (0 == nc)
    {
      strcpy(connection->daemon->nnc[off].nonce,
	     nonce);
      connection->daemon->nnc[off].nc = 0;
      (void) MHD_mutex_unlock_ (&connection->daemon->nnc_lock);
      return MHD_YES;
    }
  if ( (nc <= connection->daemon->nnc[off].nc) ||
       (0 != strcmp(connection->daemon->nnc[off].nonce, nonce)) )
    {
      (void) MHD_mutex_unlock_ (&connection->daemon->nnc_lock);
#if HAVE_MESSAGES
      MHD_DLOG (connection->daemon,
		"Stale nonce received.  If this happens a lot, you should probably increase the size of the nonce array.\n");
#endif
      return MHD_NO;
    }
  connection->daemon->nnc[off].nc = nc;
  (void) MHD_mutex_unlock_ (&connection->daemon->nnc_lock);
  return MHD_YES;
}


/**
 * Get the username from the authorization header sent by the client
 *
 * @param connection The MHD connection structure
 * @return NULL if no username could be found, a pointer
 * 			to the username if found
 * @ingroup authentication
 */
char *
MHD_digest_auth_get_username(struct MHD_Connection *connection)
{
  size_t len;
  char user[MAX_USERNAME_LENGTH];
  const char *header;

  if (NULL == (header = MHD_lookup_connection_value (connection,
						     MHD_HEADER_KIND,
						     MHD_HTTP_HEADER_AUTHORIZATION)))
    return NULL;
  if (0 != strncmp (header, _BASE, strlen (_BASE)))
    return NULL;
  header += strlen (_BASE);
  if (0 == (len = lookup_sub_value (user,
				    sizeof (user),
				    header,
				    "username")))
    return NULL;
  return strdup (user);
}


/**
 * Calculate the server nonce so that it mitigates replay attacks
 * The current format of the nonce is ...
 * H(timestamp ":" method ":" random ":" uri ":" realm) + Hex(timestamp)
 *
 * @param nonce_time The amount of time in seconds for a nonce to be invalid
 * @param method HTTP method
 * @param rnd A pointer to a character array for the random seed
 * @param rnd_size The size of the random seed array @a rnd
 * @param uri HTTP URI (in MHD, without the arguments ("?k=v")
 * @param realm A string of characters that describes the realm of auth.
 * @param nonce A pointer to a character array for the nonce to put in
 */
static void
calculate_nonce (uint32_t nonce_time,
		 const char *method,
		 const char *rnd,
		 size_t rnd_size,
		 const char *uri,
		 const char *realm,
		 char *nonce)
{
  struct MD5Context md5;
  unsigned char timestamp[4];
  unsigned char tmpnonce[MD5_DIGEST_SIZE];
  char timestamphex[sizeof(timestamp) * 2 + 1];

  MD5Init (&md5);
  timestamp[0] = (nonce_time & 0xff000000) >> 0x18;
  timestamp[1] = (nonce_time & 0x00ff0000) >> 0x10;
  timestamp[2] = (nonce_time & 0x0000ff00) >> 0x08;
  timestamp[3] = (nonce_time & 0x000000ff);
  MD5Update (&md5, timestamp, 4);
  MD5Update (&md5, ":", 1);
  MD5Update (&md5, method, strlen (method));
  MD5Update (&md5, ":", 1);
  if (rnd_size > 0)
    MD5Update (&md5, rnd, rnd_size);
  MD5Update (&md5, ":", 1);
  MD5Update (&md5, uri, strlen (uri));
  MD5Update (&md5, ":", 1);
  MD5Update (&md5, realm, strlen (realm));
  MD5Final (tmpnonce, &md5);
  cvthex (tmpnonce, sizeof (tmpnonce), nonce);
  cvthex (timestamp, 4, timestamphex);
  strncat (nonce, timestamphex, 8);
}


/**
 * Test if the given key-value pair is in the headers for the
 * given connection.
 *
 * @param connection the connection
 * @param key the key
 * @param value the value, can be NULL
 * @return #MHD_YES if the key-value pair is in the headers,
 *         #MHD_NO if not
 */
static int
test_header (struct MHD_Connection *connection,
	     const char *key,
	     const char *value)
{
  struct MHD_HTTP_Header *pos;

  for (pos = connection->headers_received; NULL != pos; pos = pos->next)
    {
      if (MHD_GET_ARGUMENT_KIND != pos->kind)
	continue;
      if (0 != strcmp (key, pos->header))
	continue;
      if ( (NULL == value) &&
	   (NULL == pos->value) )
	return MHD_YES;
      if ( (NULL == value) ||
	   (NULL == pos->value) ||
	   (0 != strcmp (value, pos->value)) )
	continue;
      return MHD_YES;
    }
  return MHD_NO;
}


/**
 * Check that the arguments given by the client as part
 * of the authentication header match the arguments we
 * got as part of the HTTP request URI.
 *
 * @param connection connections with headers to compare against
 * @param args argument URI string (after "?" in URI)
 * @return MHD_YES if the arguments match,
 *         MHD_NO if not
 */
static int
check_argument_match (struct MHD_Connection *connection,
		      const char *args)
{
  struct MHD_HTTP_Header *pos;
  char *argb;
  char *argp;
  char *equals;
  char *amper;
  unsigned int num_headers;

  argb = strdup(args);
  if (NULL == argb)
  {
#if HAVE_MESSAGES
    MHD_DLOG(connection->daemon,
             "Failed to allocate memory for copy of URI arguments\n");
#endif /* HAVE_MESSAGES */
    return MHD_NO;
  }
  num_headers = 0;
  argp = argb;
  while ( (NULL != argp) &&
	  ('\0' != argp[0]) )
    {
      equals = strchr (argp, '=');
      if (NULL == equals)
	{
	  /* add with 'value' NULL */
	  connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
						 connection,
						 argp);
	  if (MHD_YES != test_header (connection, argp, NULL))
	    return MHD_NO;
	  num_headers++;
	  break;
	}
      equals[0] = '\0';
      equals++;
      amper = strchr (equals, '&');
      if (NULL != amper)
	{
	  amper[0] = '\0';
	  amper++;
	}
      connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
					     connection,
					     argp);
      connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls,
					     connection,
					     equals);
      if (! test_header (connection, argp, equals))
	return MHD_NO;
      num_headers++;
      argp = amper;
    }

  /* also check that the number of headers matches */
  for (pos = connection->headers_received; NULL != pos; pos = pos->next)
    {
      if (MHD_GET_ARGUMENT_KIND != pos->kind)
	continue;
      num_headers--;
    }
  if (0 != num_headers)
    return MHD_NO;
  return MHD_YES;
}


/**
 * Authenticates the authorization header sent by the client
 *
 * @param connection The MHD connection structure
 * @param realm The realm presented to the client
 * @param username The username needs to be authenticated
 * @param password The password used in the authentication
 * @param nonce_timeout The amount of time for a nonce to be
 * 			invalid in seconds
 * @return #MHD_YES if authenticated, #MHD_NO if not,
 * 			#MHD_INVALID_NONCE if nonce is invalid
 * @ingroup authentication
 */
int
MHD_digest_auth_check (struct MHD_Connection *connection,
		       const char *realm,
		       const char *username,
		       const char *password,
		       unsigned int nonce_timeout)
{
  size_t len;
  const char *header;
  char *end;
  char nonce[MAX_NONCE_LENGTH];
  char cnonce[MAX_NONCE_LENGTH];
  char qop[15]; /* auth,auth-int */
  char nc[20];
  char response[MAX_AUTH_RESPONSE_LENGTH];
  const char *hentity = NULL; /* "auth-int" is not supported */
  char ha1[HASH_MD5_HEX_LEN + 1];
  char respexp[HASH_MD5_HEX_LEN + 1];
  char noncehashexp[HASH_MD5_HEX_LEN + 9];
  uint32_t nonce_time;
  uint32_t t;
  size_t left; /* number of characters left in 'header' for 'uri' */
  unsigned long int nci;

  header = MHD_lookup_connection_value (connection,
					MHD_HEADER_KIND,
					MHD_HTTP_HEADER_AUTHORIZATION);
  if (NULL == header)
    return MHD_NO;
  if (0 != strncmp(header, _BASE, strlen(_BASE)))
    return MHD_NO;
  header += strlen (_BASE);
  left = strlen (header);

  {
    char un[MAX_USERNAME_LENGTH];

    len = lookup_sub_value (un,
			    sizeof (un),
			    header, "username");
    if ( (0 == len) ||
	 (0 != strcmp(username, un)) )
      return MHD_NO;
    left -= strlen ("username") + len;
  }

  {
    char r[MAX_REALM_LENGTH];

    len = lookup_sub_value(r,
			   sizeof (r),
			   header, "realm");
    if ( (0 == len) ||
	 (0 != strcmp(realm, r)) )
      return MHD_NO;
    left -= strlen ("realm") + len;
  }

  if (0 == (len = lookup_sub_value (nonce,
				    sizeof (nonce),
				    header, "nonce")))
    return MHD_NO;
  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_NO;
  }
  {
    char *uri;
    
    uri = malloc(left + 1);
    if (NULL == uri)
    {
#if HAVE_MESSAGES
      MHD_DLOG(connection->daemon,
               "Failed to allocate memory for auth header processing\n");
#endif /* HAVE_MESSAGES */
      return MHD_NO;
    }
    if (0 == lookup_sub_value (uri,
                               left + 1,
                               header, "uri"))
    {
      free(uri);
      return MHD_NO;
    }

    /* 8 = 4 hexadecimal numbers for the timestamp */
    nonce_time = strtoul (nonce + len - 8, (char **)NULL, 16);
    t = (uint32_t) MHD_monotonic_time();
    /*
     * First level vetting for the nonce validity: if the timestamp
     * attached to the nonce exceeds `nonce_timeout', then the nonce is
     * invalid.
     */
    if ( (t > nonce_time + nonce_timeout) ||
	 (nonce_time + nonce_timeout < nonce_time) )
    { 
      free(uri);
      return MHD_INVALID_NONCE;
    }
    if (0 != strncmp (uri,
		      connection->url,
		      strlen (connection->url)))
    {
#if HAVE_MESSAGES
      MHD_DLOG (connection->daemon,
		"Authentication failed, URI does not match.\n");
#endif
      free(uri);
      return MHD_NO;
    }
    {
      const char *args = strchr (uri, '?');

      if (NULL == args)
	args = "";
      else
	args++;
      if (MHD_YES !=
	  check_argument_match (connection,
				args) )
      {
#if HAVE_MESSAGES
	MHD_DLOG (connection->daemon,
		  "Authentication failed, arguments do not match.\n");
#endif
       free(uri);
       return MHD_NO;
      }
    }
    calculate_nonce (nonce_time,
		     connection->method,
		     connection->daemon->digest_auth_random,
		     connection->daemon->digest_auth_rand_size,
		     connection->url,
		     realm,
		     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))
    {
      free(uri);
      return MHD_INVALID_NONCE;
    }
    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 == lookup_sub_value (nc, sizeof (nc), header, "nc"))  ||
	 (0 == lookup_sub_value (response, sizeof (response), header, "response")) )
    {
#if HAVE_MESSAGES
      MHD_DLOG (connection->daemon,
		"Authentication failed, invalid format.\n");
#endif
      free(uri);
      return MHD_NO;
    }
    nci = strtoul (nc, &end, 16);
    if ( ('\0' != *end) ||
	 ( (LONG_MAX == nci) &&
	   (ERANGE == errno) ) )
    {
#if HAVE_MESSAGES
      MHD_DLOG (connection->daemon,
		"Authentication failed, invalid format.\n");
#endif
      free(uri);
      return MHD_NO; /* invalid nonce format */
    }
    /*
     * Checking if that combination of nonce and nc is sound
     * and not a replay attack attempt. Also adds the nonce
     * to the nonce-nc map if it does not exist there.
     */

    if (MHD_YES != check_nonce_nc (connection, nonce, nci))
    {
      free(uri);
      return MHD_NO;
    }

    digest_calc_ha1("md5",
		    username,
		    realm,
		    password,
		    nonce,
		    cnonce,
		    ha1);
    digest_calc_response (ha1,
			  nonce,
			  nc,
			  cnonce,
			  qop,
			  connection->method,
			  uri,
			  hentity,
			  respexp);
    free(uri);
    return (0 == strcmp(response, respexp))
      ? MHD_YES
      : MHD_NO;
  }
}


/**
 * Queues a response to request authentication from the client
 *
 * @param connection The MHD connection structure
 * @param realm the realm presented to the client
 * @param opaque string to user for opaque value
 * @param response reply to send; should contain the "access denied"
 *        body; note that this function will set the "WWW Authenticate"
 *        header and that the caller should not do this
 * @param signal_stale #MHD_YES if the nonce is invalid to add
 * 			'stale=true' to the authentication header
 * @return #MHD_YES on success, #MHD_NO otherwise
 * @ingroup authentication
 */
int
MHD_queue_auth_fail_response (struct MHD_Connection *connection,
			      const char *realm,
			      const char *opaque,
			      struct MHD_Response *response,
			      int signal_stale)
{
  int ret;
  size_t hlen;
  char nonce[HASH_MD5_HEX_LEN + 9];

  /* Generating the server nonce */
  calculate_nonce ((uint32_t) MHD_monotonic_time(),
		   connection->method,
		   connection->daemon->digest_auth_random,
		   connection->daemon->digest_auth_rand_size,
		   connection->url,
		   realm,
		   nonce);
  if (MHD_YES != check_nonce_nc (connection, nonce, 0))
    {
#if HAVE_MESSAGES
      MHD_DLOG (connection->daemon,
		"Could not register nonce (is the nonce array size zero?).\n");
#endif
      return MHD_NO;
    }
  /* Building the authentication header */
  hlen = MHD_snprintf_(NULL,
		   0,
		   "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
		   realm,
		   nonce,
		   opaque,
		   signal_stale
		   ? ",stale=\"true\""
		   : "");
  {
    char *header;
    
    header = malloc(hlen + 1);
    if (NULL == header)
    {
#if HAVE_MESSAGES
      MHD_DLOG(connection->daemon,
               "Failed to allocate memory for auth response header\n");
#endif /* HAVE_MESSAGES */
      return MHD_NO;
    }

    MHD_snprintf_(header,
	      hlen + 1,
	      "Digest realm=\"%s\",qop=\"auth\",nonce=\"%s\",opaque=\"%s\"%s",
	      realm,
	      nonce,
	      opaque,
	      signal_stale
	      ? ",stale=\"true\""
	      : "");
    ret = MHD_add_response_header(response,
				  MHD_HTTP_HEADER_WWW_AUTHENTICATE,
				  header);
    free(header);
  }
  if (MHD_YES == ret)
    ret = MHD_queue_response(connection,
			     MHD_HTTP_UNAUTHORIZED,
			     response);
  return ret;
}


/* end of digestauth.c */