mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-27 04:09:31 +03:00
1075 lines
32 KiB
C
1075 lines
32 KiB
C
/*
|
|
This file is part of libmicrohttpd
|
|
Copyright (C) 2010 Christian Grothoff (and other contributing authors)
|
|
Copyright (C) 2016-2024 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, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
/**
|
|
* @file digest_auth_example_adv.c
|
|
* @brief Advanced example for digest auth with libmicrohttpd
|
|
* @author Karlson2k (Evgeny Grin)
|
|
*/
|
|
|
|
#include <microhttpd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#if ! defined(_WIN32) || defined(__CYGWIN__)
|
|
# include <errno.h>
|
|
# include <fcntl.h>
|
|
# include <unistd.h>
|
|
#else /* Native W32 */
|
|
# include <wincrypt.h>
|
|
#endif /* Native W32 */
|
|
|
|
#define SEC_AREA1_URL "/secret_page/"
|
|
#define SEC_AREA2_URL "/super_secret_page/"
|
|
|
|
#define MAIN_PAGE \
|
|
"<html><head><title>Welcome to the site</title></head>" \
|
|
"<body><p><a href=\"" SEC_AREA1_URL "\">Restricted Page</a></p>" \
|
|
"<p><a href=\"" SEC_AREA2_URL "\">Very Restricted Page</a></p></body></html>"
|
|
|
|
#define OPAQUE_DATA "ServerOpaqueData"
|
|
|
|
#define REALM "authenticated_users@thishost"
|
|
|
|
/**
|
|
* Force select "MD5" algorithm instead of MHD default (currently the same) if non-zero.
|
|
*/
|
|
static int force_md5 = 0;
|
|
/**
|
|
* Force select "SHA-256" algorithm instead of MHD default (MD5) if non-zero.
|
|
*/
|
|
static int force_sha256 = 0;
|
|
/**
|
|
* Force select "SHA-512/256" algorithm instead of MHD default (MD5) if non-zero.
|
|
*/
|
|
static int force_sha512_256 = 0;
|
|
/**
|
|
* Disable fallback to (less secure) RFC2069 if non-zero.
|
|
*/
|
|
static int allow_rfc2069 = 0;
|
|
|
|
/**
|
|
* The daemon's port
|
|
*/
|
|
static uint16_t daemon_port = 0;
|
|
|
|
|
|
/* *** "Database" of users and "database" functions *** */
|
|
|
|
/**
|
|
* User record.
|
|
* This kind of data (or something similar) should be stored in some database
|
|
* or file.
|
|
*/
|
|
struct UserEntry
|
|
{
|
|
/**
|
|
* The username.
|
|
* Static data is used in this example.
|
|
* In real application dynamic buffer or fixed size array could be used.
|
|
*/
|
|
const char *username;
|
|
#if 0 /* Disabled code */
|
|
/* The cleartext password is not stored in the database.
|
|
The more secure "userdigest" is used instead. */
|
|
/**
|
|
* The password.
|
|
* Static data is used in this example.
|
|
* In real application dynamic buffer or fixed size array could be used.
|
|
*/
|
|
const char *password;
|
|
#endif /* Disabled code */
|
|
/**
|
|
* The realm for this entry.
|
|
* Static data is used in this example.
|
|
* In real application dynamic buffer or fixed size array could be used.
|
|
*/
|
|
const char *realm;
|
|
|
|
/**
|
|
* The MD5 hash of the username together with the realm.
|
|
* This hash can be used by the client to send the username in encrypted
|
|
* form.
|
|
* The purpose of userhash is to hide user identity when transmitting
|
|
* requests over insecure link.
|
|
*/
|
|
uint8_t userhash_md5[MHD_MD5_DIGEST_SIZE];
|
|
/**
|
|
* The MD5 hash of the username with the password and the realm.
|
|
* It is used to verify that password used by the client matches password
|
|
* required by the server.
|
|
* The purpose of userhash is to avoid keeping the password in cleartext
|
|
* on the server side.
|
|
*/
|
|
uint8_t userdigest_md5[MHD_MD5_DIGEST_SIZE];
|
|
|
|
/**
|
|
* The SHA-256 hash of the username together with the realm.
|
|
* This hash can be used by the client to send the username in encrypted
|
|
* form.
|
|
* The purpose of userhash is to hide user identity when transmitting
|
|
* requests over insecure link.
|
|
*/
|
|
uint8_t userhash_sha256[MHD_SHA256_DIGEST_SIZE];
|
|
/**
|
|
* The SHA-256 hash of the username with the password and the realm.
|
|
* It is used to verify that password used by the client matches password
|
|
* required by the server.
|
|
* The purpose of userhash is to avoid keeping the password in cleartext
|
|
* on the server side.
|
|
*/
|
|
uint8_t userdigest_sha256[MHD_SHA256_DIGEST_SIZE];
|
|
|
|
/**
|
|
* The SHA-512/256 hash of the username together with the realm.
|
|
* This hash can be used by the client to send the username in encrypted
|
|
* form.
|
|
* The purpose of userhash is to hide user identity when transmitting
|
|
* requests over insecure link.
|
|
*/
|
|
uint8_t userhash_sha512_256[MHD_SHA512_256_DIGEST_SIZE];
|
|
/**
|
|
* The SHA-512/256 hash of the username with the password and the realm.
|
|
* It is used to verify that password used by the client matches password
|
|
* required by the server.
|
|
* The purpose of userhash is to avoid keeping the password in cleartext
|
|
* on the server side.
|
|
*/
|
|
uint8_t userdigest_sha512_256[MHD_SHA512_256_DIGEST_SIZE];
|
|
|
|
/**
|
|
* User has access to "area 1" if non-zero
|
|
*/
|
|
int allow_area_1;
|
|
|
|
/**
|
|
* User has access to "area 2" if non-zero
|
|
*/
|
|
int allow_area_2;
|
|
};
|
|
|
|
/**
|
|
* The array of user entries.
|
|
* In real application it should be loaded from external sources
|
|
* at the application startup.
|
|
*/
|
|
static struct UserEntry user_ids[2];
|
|
|
|
/**
|
|
* The number of entries used in @a user_ids.
|
|
*/
|
|
static size_t user_ids_used = 0;
|
|
|
|
/**
|
|
* Add new user to the users database/array.
|
|
*
|
|
* This kind of function must be used only when the new user is introduced.
|
|
* It must not be used at the every start of the application. The database
|
|
* of users should be stored somewhere and reloaded when application is
|
|
* started.
|
|
*
|
|
* @param username the username of the new user
|
|
* @param password the password of the new user
|
|
* @param realm the realm (the protection space) for which the new user
|
|
* is added
|
|
* @param allow_area_1 if non-zero than user has access to the "area 1"
|
|
* @param allow_area_2 if non-zero than user has access to the "area 2"
|
|
* @return non-zero on success,
|
|
* zero on failure (like no more space in the database).
|
|
*/
|
|
static int
|
|
add_new_user_entry (const char *const username,
|
|
const char *const password,
|
|
const char *const realm,
|
|
int allow_area_1,
|
|
int allow_area_2)
|
|
{
|
|
struct UserEntry *entry;
|
|
enum MHD_Result res;
|
|
|
|
if ((sizeof(user_ids) / sizeof(user_ids[0])) <= user_ids_used)
|
|
return 0; /* No more space to add new entry */
|
|
|
|
entry = user_ids + user_ids_used;
|
|
|
|
entry->username = username;
|
|
entry->realm = realm;
|
|
|
|
res = MHD_YES;
|
|
|
|
if (MHD_NO != res)
|
|
res = MHD_digest_auth_calc_userhash (MHD_DIGEST_AUTH_ALGO3_MD5,
|
|
username,
|
|
realm,
|
|
entry->userhash_md5,
|
|
sizeof(entry->userhash_md5));
|
|
if (MHD_NO != res)
|
|
res = MHD_digest_auth_calc_userdigest (MHD_DIGEST_AUTH_ALGO3_MD5,
|
|
username,
|
|
realm,
|
|
password,
|
|
entry->userdigest_md5,
|
|
sizeof(entry->userdigest_md5));
|
|
|
|
if (MHD_NO != res)
|
|
res = MHD_digest_auth_calc_userhash (MHD_DIGEST_AUTH_ALGO3_SHA256,
|
|
username,
|
|
realm,
|
|
entry->userhash_sha256,
|
|
sizeof(entry->userhash_sha256));
|
|
if (MHD_NO != res)
|
|
res = MHD_digest_auth_calc_userdigest (MHD_DIGEST_AUTH_ALGO3_SHA256,
|
|
username,
|
|
realm,
|
|
password,
|
|
entry->userdigest_sha256,
|
|
sizeof(entry->userdigest_sha256));
|
|
|
|
if (MHD_NO != res)
|
|
res = MHD_digest_auth_calc_userhash (MHD_DIGEST_AUTH_ALGO3_SHA512_256,
|
|
username,
|
|
realm,
|
|
entry->userhash_sha512_256,
|
|
sizeof(entry->userhash_sha512_256));
|
|
if (MHD_NO != res)
|
|
res =
|
|
MHD_digest_auth_calc_userdigest (MHD_DIGEST_AUTH_ALGO3_SHA512_256,
|
|
username,
|
|
realm,
|
|
password,
|
|
entry->userdigest_sha512_256,
|
|
sizeof(entry->userdigest_sha512_256));
|
|
|
|
if (MHD_NO == res)
|
|
return 0; /* Failure exit point */
|
|
|
|
entry->allow_area_1 = allow_area_1;
|
|
entry->allow_area_2 = allow_area_2;
|
|
|
|
user_ids_used++;
|
|
|
|
return ! 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find the user entry for specified username
|
|
* @param username the username to find
|
|
* @return NULL if no entry for specified username is found,
|
|
* pointer to user entry if found
|
|
*/
|
|
static struct UserEntry *
|
|
find_entry_by_username (const char *const username)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < (sizeof(user_ids) / sizeof(user_ids[0])); ++i)
|
|
{
|
|
struct UserEntry *entry;
|
|
|
|
entry = user_ids + i;
|
|
if (0 == strcmp (username, entry->username))
|
|
return entry;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find the user entry for specified userhash
|
|
* @param algo3 the algorithm used for userhash calculation
|
|
* @param userhash the userhash identifier to find
|
|
* @param userhash_size the size @a userhash in bytes
|
|
* @return NULL if no entry for specified userhash is found,
|
|
* pointer to user entry if found
|
|
*/
|
|
static struct UserEntry *
|
|
find_entry_by_userhash (enum MHD_DigestAuthAlgo3 algo3,
|
|
const void *userhash,
|
|
size_t userhash_size)
|
|
{
|
|
size_t i;
|
|
|
|
if (MHD_digest_get_hash_size (algo3) != userhash_size)
|
|
return NULL; /* Wrong length of the userhash */
|
|
|
|
switch (algo3)
|
|
{
|
|
case MHD_DIGEST_AUTH_ALGO3_MD5:
|
|
case MHD_DIGEST_AUTH_ALGO3_MD5_SESSION: /* An extra case not used currently */
|
|
if (sizeof(user_ids[0].userhash_md5) != userhash_size) /* Extra check. The size was checked before */
|
|
return NULL;
|
|
for (i = 0; i < (sizeof(user_ids) / sizeof(user_ids[0])); ++i)
|
|
{
|
|
struct UserEntry *entry;
|
|
|
|
entry = user_ids + i;
|
|
if (0 == memcmp (userhash, entry->userhash_md5,
|
|
sizeof(entry->userhash_md5)))
|
|
return entry;
|
|
}
|
|
break;
|
|
case MHD_DIGEST_AUTH_ALGO3_SHA256:
|
|
case MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION: /* An extra case not used currently */
|
|
if (sizeof(user_ids[0].userhash_sha256) != userhash_size) /* Extra check. The size was checked before */
|
|
return NULL;
|
|
for (i = 0; i < (sizeof(user_ids) / sizeof(user_ids[0])); ++i)
|
|
{
|
|
struct UserEntry *entry;
|
|
|
|
entry = user_ids + i;
|
|
if (0 == memcmp (userhash, entry->userhash_sha256,
|
|
sizeof(entry->userhash_sha256)))
|
|
return entry;
|
|
}
|
|
break;
|
|
case MHD_DIGEST_AUTH_ALGO3_SHA512_256:
|
|
case MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION: /* An extra case not used currently */
|
|
if (sizeof(user_ids[0].userhash_sha512_256) != userhash_size) /* Extra check. The size was checked before */
|
|
return NULL;
|
|
for (i = 0; i < (sizeof(user_ids) / sizeof(user_ids[0])); ++i)
|
|
{
|
|
struct UserEntry *entry;
|
|
|
|
entry = user_ids + i;
|
|
if (0 == memcmp (userhash, entry->userhash_sha512_256,
|
|
sizeof(entry->userhash_sha512_256)))
|
|
return entry;
|
|
}
|
|
break;
|
|
case MHD_DIGEST_AUTH_ALGO3_INVALID: /* Mute compiler warning. Impossible value in this context. */
|
|
default:
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find the user entry for the user specified by provided username info
|
|
* @param user_info the pointer to the structure username info returned by MHD
|
|
* @return NULL if no entry for specified username info is found,
|
|
* pointer to user entry if found
|
|
*/
|
|
static struct UserEntry *
|
|
find_entry_by_userinfo (const struct MHD_DigestAuthUsernameInfo *username_info)
|
|
{
|
|
if (MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD <= username_info->uname_type)
|
|
return find_entry_by_username (username_info->username);
|
|
|
|
if (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == username_info->uname_type)
|
|
return find_entry_by_userhash (username_info->algo3,
|
|
username_info->userhash_bin,
|
|
username_info->userhash_hex_len / 2);
|
|
|
|
return NULL; /* Should be unreachable as all cases are covered before */
|
|
}
|
|
|
|
|
|
/* *** End of "database" of users and "database" functions *** */
|
|
|
|
/* *** Requests handling *** */
|
|
|
|
/**
|
|
* Send "Requested HTTP method is not supported" page
|
|
* @param c the connection structure
|
|
* @return MHD_YES if response was successfully queued,
|
|
* MHD_NO otherwise
|
|
*/
|
|
static enum MHD_Result
|
|
reply_with_page_not_found (struct MHD_Connection *c)
|
|
{
|
|
static const char page_content[] =
|
|
"<html><head><title>Page Not Found</title></head>" \
|
|
"<body>The requested page not found.</body></html>";
|
|
static const size_t page_content_len =
|
|
(sizeof(page_content) / sizeof(char)) - 1;
|
|
struct MHD_Response *resp;
|
|
enum MHD_Result ret;
|
|
|
|
resp = MHD_create_response_from_buffer_static (page_content_len,
|
|
page_content);
|
|
if (NULL == resp)
|
|
return MHD_NO;
|
|
|
|
/* Ignore possible error when adding the header as the reply will work even
|
|
without this header. */
|
|
(void) MHD_add_response_header (resp,
|
|
MHD_HTTP_HEADER_CONTENT_TYPE,
|
|
"text/html");
|
|
|
|
ret = MHD_queue_response (c, MHD_HTTP_NOT_FOUND, resp);
|
|
MHD_destroy_response (resp);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get enum MHD_DigestAuthMultiAlgo3 value to be used for authentication.
|
|
* @return the algorithm number/value
|
|
*/
|
|
static enum MHD_DigestAuthMultiAlgo3
|
|
get_m_algo (void)
|
|
{
|
|
if (force_md5)
|
|
return MHD_DIGEST_AUTH_MULT_ALGO3_MD5;
|
|
else if (force_sha256)
|
|
return MHD_DIGEST_AUTH_MULT_ALGO3_SHA256;
|
|
else if (force_sha512_256)
|
|
return MHD_DIGEST_AUTH_MULT_ALGO3_SHA512_256;
|
|
|
|
/* No forced algorithm selection, let MHD to use default */
|
|
return MHD_DIGEST_AUTH_MULT_ALGO3_ANY_NON_SESSION;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get enum MHD_DigestAuthMultiQOP value to be used for authentication.
|
|
* @return the "Quality Of Protection" number/value
|
|
*/
|
|
static enum MHD_DigestAuthMultiQOP
|
|
get_m_QOP (void)
|
|
{
|
|
if (allow_rfc2069)
|
|
return MHD_DIGEST_AUTH_MULT_QOP_ANY_NON_INT;
|
|
|
|
return MHD_DIGEST_AUTH_MULT_QOP_AUTH;
|
|
}
|
|
|
|
|
|
/**
|
|
* Send "Authentication required" page
|
|
* @param c the connection structure
|
|
* @param stale if non-zero then "nonce stale" is indicated in the reply
|
|
* @param wrong_cred if non-zero then client is informed the previously
|
|
* it used wrong credentials
|
|
* @return MHD_YES if response was successfully queued,
|
|
* MHD_NO otherwise
|
|
*/
|
|
static enum MHD_Result
|
|
reply_with_auth_required (struct MHD_Connection *c,
|
|
int stale,
|
|
int wrong_cred)
|
|
{
|
|
static const char auth_required_content[] =
|
|
"<html><head><title>Authentication required</title></head>" \
|
|
"<body>The requested page needs authentication.</body></html>";
|
|
static const size_t auth_required_content_len =
|
|
(sizeof(auth_required_content) / sizeof(char)) - 1;
|
|
static const char wrong_creds_content[] =
|
|
"<html><head><title>Wrong credentials</title></head>" \
|
|
"<body>The provided credentials are incorrect.</body></html>";
|
|
static const size_t wrong_creds_content_len =
|
|
(sizeof(wrong_creds_content) / sizeof(char)) - 1;
|
|
struct MHD_Response *resp;
|
|
enum MHD_Result ret;
|
|
|
|
if (wrong_cred)
|
|
stale = 0; /* Force client to ask user for username and password */
|
|
|
|
if (! wrong_cred)
|
|
resp = MHD_create_response_from_buffer_static (auth_required_content_len,
|
|
auth_required_content);
|
|
else
|
|
resp = MHD_create_response_from_buffer_static (wrong_creds_content_len,
|
|
wrong_creds_content);
|
|
if (NULL == resp)
|
|
return MHD_NO;
|
|
|
|
/* Ignore possible error when adding the header as the reply will work even
|
|
without this header. */
|
|
(void) MHD_add_response_header (resp,
|
|
MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
|
|
|
|
|
|
ret = MHD_queue_auth_required_response3 (
|
|
c,
|
|
REALM,
|
|
OPAQUE_DATA, /* The "opaque data", not really useful */
|
|
SEC_AREA1_URL " " SEC_AREA2_URL, /* Space-separated list of URLs' initial parts */
|
|
resp,
|
|
stale,
|
|
get_m_QOP (),
|
|
get_m_algo (),
|
|
! 0, /* Userhash support enabled */
|
|
! 0 /* UTF-8 is preferred */);
|
|
MHD_destroy_response (resp);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Send "Forbidden" page
|
|
* @param c the connection structure
|
|
* @return MHD_YES if response was successfully queued,
|
|
* MHD_NO otherwise
|
|
*/
|
|
static enum MHD_Result
|
|
reply_with_forbidden (struct MHD_Connection *c)
|
|
{
|
|
static const char page_content[] =
|
|
"<html><head><title>Forbidden</title></head>" \
|
|
"<body>You do not have access to this page.</body></html>";
|
|
static const size_t page_content_len =
|
|
(sizeof(page_content) / sizeof(char)) - 1;
|
|
struct MHD_Response *resp;
|
|
enum MHD_Result ret;
|
|
|
|
resp = MHD_create_response_from_buffer_static (page_content_len, page_content)
|
|
;
|
|
if (NULL == resp)
|
|
return MHD_NO;
|
|
|
|
/* Ignore possible error when adding the header as the reply will work even
|
|
without this header. */
|
|
(void) MHD_add_response_header (resp,
|
|
MHD_HTTP_HEADER_CONTENT_TYPE,
|
|
"text/html");
|
|
|
|
ret = MHD_queue_response (c, MHD_HTTP_FORBIDDEN, resp);
|
|
MHD_destroy_response (resp);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Send "Area 1" pages
|
|
* @param c the connection structure
|
|
* @param url the requested URL
|
|
* @return MHD_YES if response was successfully queued,
|
|
* MHD_NO otherwise
|
|
*/
|
|
static enum MHD_Result
|
|
reply_with_area1_pages (struct MHD_Connection *c,
|
|
const char *url)
|
|
{
|
|
|
|
if (0 == strcmp (url, SEC_AREA1_URL ""))
|
|
{
|
|
static const char page_content[] =
|
|
"<html><head><title>Restricted secret page</title></head>" \
|
|
"<body>Welcome to the restricted area</body></html>";
|
|
static const size_t page_content_len =
|
|
(sizeof(page_content) / sizeof(char)) - 1;
|
|
struct MHD_Response *resp;
|
|
enum MHD_Result ret;
|
|
|
|
resp = MHD_create_response_from_buffer_static (page_content_len,
|
|
page_content);
|
|
if (NULL == resp)
|
|
return MHD_NO;
|
|
|
|
/* Ignore possible error when adding the header as the reply will work even
|
|
without this header. */
|
|
(void) MHD_add_response_header (resp, MHD_HTTP_HEADER_CONTENT_TYPE,
|
|
"text/html");
|
|
|
|
ret = MHD_queue_response (c, MHD_HTTP_OK, resp);
|
|
MHD_destroy_response (resp);
|
|
return ret;
|
|
}
|
|
/* If needed: add handlers for other URLs in this area */
|
|
#if 0 /* Disabled code */
|
|
if (0 == strcmp (url, SEC_AREA1_URL "some_path/some_page"))
|
|
{
|
|
/* Add page creation/processing code */
|
|
}
|
|
#endif /* Disabled code */
|
|
|
|
/* The requested URL is unknown */
|
|
return reply_with_page_not_found (c);
|
|
}
|
|
|
|
|
|
/**
|
|
* Send "Area 2" pages
|
|
* @param c the connection structure
|
|
* @param url the requested URL
|
|
* @return MHD_YES if response was successfully queued,
|
|
* MHD_NO otherwise
|
|
*/
|
|
static enum MHD_Result
|
|
reply_with_area2_pages (struct MHD_Connection *c,
|
|
const char *url)
|
|
{
|
|
|
|
if (0 == strcmp (url, SEC_AREA2_URL ""))
|
|
{
|
|
static const char page_content[] =
|
|
"<html><head><title>Very restricted secret page</title></head>" \
|
|
"<body>Welcome to the super restricted area</body></html>";
|
|
static const size_t page_content_len =
|
|
(sizeof(page_content) / sizeof(char)) - 1;
|
|
struct MHD_Response *resp;
|
|
enum MHD_Result ret;
|
|
|
|
resp = MHD_create_response_from_buffer_static (page_content_len,
|
|
page_content);
|
|
if (NULL == resp)
|
|
return MHD_NO;
|
|
|
|
/* Ignore possible error when adding the header as the reply will work even
|
|
without this header. */
|
|
(void) MHD_add_response_header (resp, MHD_HTTP_HEADER_CONTENT_TYPE,
|
|
"text/html");
|
|
|
|
ret = MHD_queue_response (c, MHD_HTTP_OK, resp);
|
|
MHD_destroy_response (resp);
|
|
return ret;
|
|
}
|
|
/* If needed: add handlers for other URLs in this area */
|
|
#if 0 /* Disabled code */
|
|
if (0 == strcmp (url, SEC_AREA2_URL "other_path/other_page"))
|
|
{
|
|
/* Add page creation/processing code */
|
|
}
|
|
#endif /* Disabled code */
|
|
|
|
/* The requested URL is unknown */
|
|
return reply_with_page_not_found (c);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle client's request for secured areas
|
|
* @param c the connection structure
|
|
* @param url the URL requested by the client
|
|
* @param sec_area_num the number of secured area
|
|
* @return MHD_YES if request was handled (either with "denied" or with
|
|
* "allowed" result),
|
|
* MHD_NO if it was an error handling the request.
|
|
*/
|
|
static enum MHD_Result
|
|
handle_sec_areas_req (struct MHD_Connection *c, const char *url, unsigned int
|
|
sec_area_num)
|
|
{
|
|
struct MHD_DigestAuthUsernameInfo *username_info;
|
|
struct UserEntry *user_entry;
|
|
void *userdigest;
|
|
size_t userdigest_size;
|
|
enum MHD_DigestAuthResult auth_res;
|
|
|
|
username_info = MHD_digest_auth_get_username3 (c);
|
|
|
|
if (NULL == username_info)
|
|
return reply_with_auth_required (c, 0, 0);
|
|
|
|
user_entry = find_entry_by_userinfo (username_info);
|
|
|
|
if (NULL == user_entry)
|
|
return reply_with_auth_required (c, 0, 1);
|
|
|
|
switch (username_info->algo3)
|
|
{
|
|
case MHD_DIGEST_AUTH_ALGO3_MD5:
|
|
userdigest = user_entry->userdigest_md5;
|
|
userdigest_size = sizeof(user_entry->userdigest_md5);
|
|
break;
|
|
case MHD_DIGEST_AUTH_ALGO3_SHA256:
|
|
userdigest = user_entry->userdigest_sha256;
|
|
userdigest_size = sizeof(user_entry->userdigest_sha256);
|
|
break;
|
|
case MHD_DIGEST_AUTH_ALGO3_SHA512_256:
|
|
userdigest = user_entry->userdigest_sha512_256;
|
|
userdigest_size = sizeof(user_entry->userdigest_sha512_256);
|
|
break;
|
|
case MHD_DIGEST_AUTH_ALGO3_MD5_SESSION:
|
|
case MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION:
|
|
case MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION:
|
|
/* Not supported currently and not used by MHD.
|
|
The client incorrectly used algorithm not advertised by the server. */
|
|
return reply_with_auth_required (c, 0, 1);
|
|
case MHD_DIGEST_AUTH_ALGO3_INVALID: /* Mute compiler warning */
|
|
default:
|
|
return MHD_NO; /* Should be unreachable */
|
|
}
|
|
|
|
auth_res = MHD_digest_auth_check_digest3 (
|
|
c,
|
|
REALM, /* Make sure to use the proper realm, not the realm provided by the client and returned by "user_entry" */
|
|
user_entry->username,
|
|
userdigest,
|
|
userdigest_size,
|
|
0, /* Use daemon's default value for nonce_timeout*/
|
|
0, /* Use daemon's default value for max_nc */
|
|
get_m_QOP (),
|
|
(enum MHD_DigestAuthMultiAlgo3) username_info->algo3 /* Direct cast from "single algorithm" to "multi-algorithm" is allowed */
|
|
);
|
|
|
|
if (MHD_DAUTH_OK != auth_res)
|
|
{
|
|
int need_just_refresh_nonce;
|
|
/* Actually MHD_DAUTH_NONCE_OTHER_COND should not be returned as
|
|
MHD_OPTION_DIGEST_AUTH_NONCE_BIND_TYPE is not used for the daemon.
|
|
To keep the code universal the MHD_DAUTH_NONCE_OTHER_COND is
|
|
still checked here. */
|
|
need_just_refresh_nonce =
|
|
(MHD_DAUTH_NONCE_STALE == auth_res)
|
|
|| (MHD_DAUTH_NONCE_OTHER_COND == auth_res);
|
|
return reply_with_auth_required (c,
|
|
need_just_refresh_nonce,
|
|
! need_just_refresh_nonce);
|
|
}
|
|
|
|
/* The user successfully authenticated */
|
|
|
|
/* Check whether access to the request area is allowed for the user */
|
|
if (1 == sec_area_num)
|
|
{
|
|
if (user_entry->allow_area_1)
|
|
return reply_with_area1_pages (c, url);
|
|
else
|
|
return reply_with_forbidden (c);
|
|
}
|
|
else if (2 == sec_area_num)
|
|
{
|
|
if (user_entry->allow_area_2)
|
|
return reply_with_area2_pages (c, url);
|
|
else
|
|
return reply_with_forbidden (c);
|
|
}
|
|
|
|
return MHD_NO; /* Should be unreachable */
|
|
}
|
|
|
|
|
|
/**
|
|
* Send the main page
|
|
* @param c the connection structure
|
|
* @return MHD_YES if response was successfully queued,
|
|
* MHD_NO otherwise
|
|
*/
|
|
static enum MHD_Result
|
|
reply_with_main_page (struct MHD_Connection *c)
|
|
{
|
|
static const char page_content[] = MAIN_PAGE;
|
|
static const size_t page_content_len =
|
|
(sizeof(page_content) / sizeof(char)) - 1;
|
|
struct MHD_Response *resp;
|
|
enum MHD_Result ret;
|
|
|
|
resp = MHD_create_response_from_buffer_static (page_content_len, page_content)
|
|
;
|
|
if (NULL == resp)
|
|
return MHD_NO;
|
|
|
|
/* Ignore possible error when adding the header as the reply will work even
|
|
without this header. */
|
|
(void) MHD_add_response_header (resp,
|
|
MHD_HTTP_HEADER_CONTENT_TYPE,
|
|
"text/html");
|
|
|
|
ret = MHD_queue_response (c, MHD_HTTP_OK, resp);
|
|
MHD_destroy_response (resp);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Send "Requested HTTP method is not supported" page
|
|
* @param c the connection structure
|
|
* @return MHD_YES if response was successfully queued,
|
|
* MHD_NO otherwise
|
|
*/
|
|
static enum MHD_Result
|
|
reply_with_method_not_supported (struct MHD_Connection *c)
|
|
{
|
|
static const char page_content[] =
|
|
"<html><head><title>Requested HTTP Method Is Not Supported</title></head>" \
|
|
"<body>The requested HTTP method is not supported.</body></html>";
|
|
static const size_t page_content_len =
|
|
(sizeof(page_content) / sizeof(char)) - 1;
|
|
struct MHD_Response *resp;
|
|
enum MHD_Result ret;
|
|
|
|
resp = MHD_create_response_from_buffer_static (page_content_len, page_content)
|
|
;
|
|
if (NULL == resp)
|
|
return MHD_NO;
|
|
|
|
/* Ignore possible error when adding the header as the reply will work even
|
|
without this header. */
|
|
(void) MHD_add_response_header (resp,
|
|
MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
|
|
|
|
ret = MHD_queue_response (c, MHD_HTTP_NOT_IMPLEMENTED, resp);
|
|
MHD_destroy_response (resp);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static enum MHD_Result
|
|
ahc_main (void *cls,
|
|
struct MHD_Connection *connection,
|
|
const char *url,
|
|
const char *method,
|
|
const char *version,
|
|
const char *upload_data, size_t *upload_data_size,
|
|
void **req_cls)
|
|
{
|
|
static int already_called_marker;
|
|
size_t url_len;
|
|
(void) cls; /* Unused. Silent compiler warning. */
|
|
(void) version; /* Unused. Silent compiler warning. */
|
|
(void) upload_data; /* Unused. Silent compiler warning. */
|
|
|
|
if ((0 != strcmp (method, MHD_HTTP_METHOD_GET))
|
|
&& (0 != strcmp (method, MHD_HTTP_METHOD_HEAD)))
|
|
return reply_with_method_not_supported (connection);
|
|
|
|
if (0 != *upload_data_size)
|
|
return MHD_NO; /* No upload expected for GET or HEAD */
|
|
|
|
if (&already_called_marker != *req_cls)
|
|
{ /* Called for the first time, request not fully read yet */
|
|
*req_cls = &already_called_marker;
|
|
/* Wait for complete request */
|
|
return MHD_YES;
|
|
}
|
|
|
|
if (0 == strcmp (url, "/"))
|
|
return reply_with_main_page (connection);
|
|
|
|
url_len = strlen (url);
|
|
|
|
if ((strlen (SEC_AREA1_URL) <= url_len)
|
|
&& (0 == memcmp (url, SEC_AREA1_URL, strlen (SEC_AREA1_URL))))
|
|
return handle_sec_areas_req (connection, url, 1); /* The requested URL is within SEC_AREA1_URL */
|
|
|
|
if ((strlen (SEC_AREA2_URL) <= url_len)
|
|
&& (0 == memcmp (url, SEC_AREA2_URL, strlen (SEC_AREA2_URL))))
|
|
return handle_sec_areas_req (connection, url, 2); /* The requested URL is within SEC_AREA2_URL */
|
|
|
|
return reply_with_page_not_found (connection);
|
|
}
|
|
|
|
|
|
/* *** End of requests handling *** */
|
|
|
|
|
|
/**
|
|
* Add new users to the users "database".
|
|
*
|
|
* In real application this kind of function must NOT be called at
|
|
* the application startup. Instead similar function should be
|
|
* called only when new user is introduced. The users "database"
|
|
* should be stored somewhere and reloaded at the application
|
|
* startup.
|
|
*
|
|
* @return non-zero on success,
|
|
* zero in case of error.
|
|
*/
|
|
static int
|
|
add_new_users (void)
|
|
{
|
|
if (! add_new_user_entry ("joepublic",
|
|
"password",
|
|
REALM,
|
|
! 0,
|
|
0))
|
|
return 0;
|
|
|
|
if (! add_new_user_entry ("superadmin",
|
|
"pA$$w0Rd",
|
|
REALM,
|
|
! 0,
|
|
! 0))
|
|
return 0;
|
|
|
|
return ! 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check and apply application parameters
|
|
* @param argc the argc of the @a main function
|
|
* @param argv the argv of the @a main function
|
|
* @return non-zero on success,
|
|
* zero in case of any error (like wrong parameters).
|
|
*/
|
|
static int
|
|
check_params (int argc, char *const *const argv)
|
|
{
|
|
size_t i;
|
|
unsigned int port_value;
|
|
|
|
if (2 > argc)
|
|
return 0;
|
|
|
|
for (i = 1; i < (unsigned int) argc; ++i)
|
|
{
|
|
if (0 == strcmp (argv[i], "--md5"))
|
|
{ /* Force use MD5 */
|
|
force_md5 = ! 0;
|
|
force_sha256 = 0;
|
|
force_sha512_256 = 0;
|
|
}
|
|
else if (0 == strcmp (argv[i], "--sha256"))
|
|
{ /* Force use SHA-256 instead of default MD5 */
|
|
force_md5 = 0;
|
|
force_sha256 = ! 0;
|
|
force_sha512_256 = 0;
|
|
}
|
|
else if (0 == strcmp (argv[i], "--sha512-256"))
|
|
{ /* Force use SHA-512/256 instead of default MD5 */
|
|
force_md5 = 0;
|
|
force_sha256 = 0;
|
|
force_sha512_256 = ! 0;
|
|
}
|
|
else if (0 == strcmp (argv[i], "--allow-rfc2069"))
|
|
allow_rfc2069 = ! 0; /* Allow fallback to RFC2069. Not recommended! */
|
|
else if ((1 == sscanf (argv[i], "%u", &port_value))
|
|
&& (0 < port_value) && (65535 >= port_value))
|
|
daemon_port = (uint16_t) port_value;
|
|
else
|
|
{
|
|
fprintf (stderr, "Unrecognized parameter: %s\n",
|
|
argv[i]);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (force_sha512_256)
|
|
printf (
|
|
"Note: when testing with curl/libcurl do not be surprised with failures as "
|
|
"libcurl incorrectly implements SHA-512/256 algorithm.\n");
|
|
return ! 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* The cryptographically secure random data
|
|
*/
|
|
static uint8_t rand_data[8];
|
|
|
|
/**
|
|
* Initialise the random data
|
|
* @return non-zero if succeed,
|
|
* zero if failed
|
|
*/
|
|
static int
|
|
init_rand_data (void)
|
|
{
|
|
#if ! defined(_WIN32) || defined(__CYGWIN__)
|
|
int fd;
|
|
ssize_t len;
|
|
size_t off;
|
|
|
|
fd = open ("/dev/urandom", O_RDONLY);
|
|
if (-1 == fd)
|
|
{
|
|
fprintf (stderr, "Failed to open '%s': %s\n",
|
|
"/dev/urandom",
|
|
strerror (errno));
|
|
return 0;
|
|
}
|
|
for (off = 0; off < sizeof(rand_data); off += (size_t) len)
|
|
{
|
|
len = read (fd, rand_data, 8);
|
|
if (0 > len)
|
|
{
|
|
fprintf (stderr, "Failed to read '%s': %s\n",
|
|
"/dev/urandom",
|
|
strerror (errno));
|
|
(void) close (fd);
|
|
return 0;
|
|
}
|
|
}
|
|
(void) close (fd);
|
|
#else /* Native W32 */
|
|
HCRYPTPROV cc;
|
|
BOOL b;
|
|
|
|
b = CryptAcquireContext (&cc,
|
|
NULL,
|
|
NULL,
|
|
PROV_RSA_FULL,
|
|
CRYPT_VERIFYCONTEXT);
|
|
if (FALSE == b)
|
|
{
|
|
fprintf (stderr,
|
|
"Failed to acquire crypto provider context: %lu\n",
|
|
(unsigned long) GetLastError ());
|
|
return 0;
|
|
}
|
|
b = CryptGenRandom (cc, sizeof(rand_data), (BYTE *) rand_data);
|
|
if (FALSE == b)
|
|
{
|
|
fprintf (stderr,
|
|
"Failed to generate 8 random bytes: %lu\n",
|
|
GetLastError ());
|
|
}
|
|
CryptReleaseContext (cc, 0);
|
|
if (FALSE == b)
|
|
return 0;
|
|
#endif /* Native W32 */
|
|
|
|
return ! 0;
|
|
}
|
|
|
|
|
|
int
|
|
main (int argc, char *const *argv)
|
|
{
|
|
struct MHD_Daemon *d;
|
|
|
|
if (! check_params (argc, argv))
|
|
{
|
|
fprintf (stderr, "Usage: %s [--md5|--sha256|--sha512-256] "
|
|
"[--allow-rfc2069] PORT\n", argv[0]);
|
|
return 1;
|
|
}
|
|
if (! add_new_users ())
|
|
{
|
|
fprintf (stderr, "Failed to add new users to the users database.\n");
|
|
return 2;
|
|
}
|
|
if (! init_rand_data ())
|
|
{
|
|
fprintf (stderr, "Failed to initialise random data.\n");
|
|
return 2;
|
|
}
|
|
|
|
d = MHD_start_daemon (
|
|
MHD_USE_INTERNAL_POLLING_THREAD
|
|
| MHD_USE_THREAD_PER_CONNECTION
|
|
| MHD_USE_ERROR_LOG,
|
|
daemon_port,
|
|
NULL, NULL, &ahc_main, NULL,
|
|
MHD_OPTION_DIGEST_AUTH_RANDOM, sizeof(rand_data), rand_data,
|
|
MHD_OPTION_NONCE_NC_SIZE, 500,
|
|
MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 180,
|
|
MHD_OPTION_END);
|
|
if (d == NULL)
|
|
{
|
|
fprintf (stderr, "Failed to start the server on port %lu.\n",
|
|
(unsigned long) daemon_port);
|
|
return 1;
|
|
}
|
|
printf ("Running server on port %lu.\nPress ENTER to stop.\n",
|
|
(unsigned long) daemon_port);
|
|
(void) getc (stdin);
|
|
MHD_stop_daemon (d);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* End of digest_auth_example_adv.c */
|