| /* |
| 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 */ |