libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

digest_auth_example_adv.c (33257B)


      1 /*
      2      This file is part of libmicrohttpd
      3      Copyright (C) 2010 Christian Grothoff (and other contributing authors)
      4      Copyright (C) 2016-2024 Evgeny Grin (Karlson2k)
      5 
      6      This library is free software; you can redistribute it and/or
      7      modify it under the terms of the GNU Lesser General Public
      8      License as published by the Free Software Foundation; either
      9      version 2.1 of the License, or (at your option) any later version.
     10 
     11      This library is distributed in the hope that it will be useful,
     12      but WITHOUT ANY WARRANTY; without even the implied warranty of
     13      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14      Lesser General Public License for more details.
     15 
     16      You should have received a copy of the GNU Lesser General Public
     17      License along with this library; if not, write to the Free Software
     18      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
     19 */
     20 /**
     21  * @file digest_auth_example_adv.c
     22  * @brief Advanced example for digest auth with libmicrohttpd
     23  * @author Karlson2k (Evgeny Grin)
     24  */
     25 
     26 #include <microhttpd.h>
     27 #include <stdlib.h>
     28 #include <stdio.h>
     29 #include <stdint.h>
     30 #include <string.h>
     31 #if ! defined(_WIN32) || defined(__CYGWIN__)
     32 #  include <errno.h>
     33 #  include <fcntl.h>
     34 #  include <unistd.h>
     35 #else  /* Native W32 */
     36 #  include <wincrypt.h>
     37 #endif /* Native W32 */
     38 
     39 #define SEC_AREA1_URL "/secret_page/"
     40 #define SEC_AREA2_URL "/super_secret_page/"
     41 
     42 #define MAIN_PAGE \
     43   "<html><head><title>Welcome to the site</title></head>" \
     44   "<body><p><a href=\"" SEC_AREA1_URL "\">Restricted Page</a></p>" \
     45   "<p><a href=\"" SEC_AREA2_URL "\">Very Restricted Page</a></p></body></html>"
     46 
     47 #define OPAQUE_DATA "ServerOpaqueData"
     48 
     49 #define REALM "authenticated_users@thishost"
     50 
     51 /**
     52  * Force select "MD5" algorithm instead of MHD default (currently the same) if non-zero.
     53  */
     54 static int force_md5 = 0;
     55 /**
     56  * Force select "SHA-256" algorithm instead of MHD default (MD5) if non-zero.
     57  */
     58 static int force_sha256 = 0;
     59 /**
     60  * Force select "SHA-512/256" algorithm instead of MHD default (MD5) if non-zero.
     61  */
     62 static int force_sha512_256 = 0;
     63 /**
     64  * Disable fallback to (less secure) RFC2069 if non-zero.
     65  */
     66 static int allow_rfc2069 = 0;
     67 
     68 /**
     69  * The daemon's port
     70  */
     71 static uint16_t daemon_port = 0;
     72 
     73 
     74 /* *** "Database" of users and "database" functions *** */
     75 
     76 /**
     77  * User record.
     78  * This kind of data (or something similar) should be stored in some database
     79  * or file.
     80  */
     81 struct UserEntry
     82 {
     83   /**
     84    * The username.
     85    * Static data is used in this example.
     86    * In real application dynamic buffer or fixed size array could be used.
     87    */
     88   const char *username;
     89 #if 0 /* Disabled code */
     90   /* The cleartext password is not stored in the database.
     91      The more secure "userdigest" is used instead. */
     92   /**
     93    * The password.
     94    * Static data is used in this example.
     95    * In real application dynamic buffer or fixed size array could be used.
     96    */
     97   const char *password;
     98 #endif /* Disabled code */
     99   /**
    100    * The realm for this entry.
    101    * Static data is used in this example.
    102    * In real application dynamic buffer or fixed size array could be used.
    103    */
    104   const char *realm;
    105 
    106   /**
    107    * The MD5 hash of the username together with the realm.
    108    * This hash can be used by the client to send the username in encrypted
    109    * form.
    110    * The purpose of userhash is to hide user identity when transmitting
    111    * requests over insecure link.
    112    */
    113   uint8_t userhash_md5[MHD_MD5_DIGEST_SIZE];
    114   /**
    115    * The MD5 hash of the username with the password and the realm.
    116    * It is used to verify that password used by the client matches password
    117    * required by the server.
    118    * The purpose of userhash is to avoid keeping the password in cleartext
    119    * on the server side.
    120    */
    121   uint8_t userdigest_md5[MHD_MD5_DIGEST_SIZE];
    122 
    123   /**
    124    * The SHA-256 hash of the username together with the realm.
    125    * This hash can be used by the client to send the username in encrypted
    126    * form.
    127    * The purpose of userhash is to hide user identity when transmitting
    128    * requests over insecure link.
    129    */
    130   uint8_t userhash_sha256[MHD_SHA256_DIGEST_SIZE];
    131   /**
    132    * The SHA-256 hash of the username with the password and the realm.
    133    * It is used to verify that password used by the client matches password
    134    * required by the server.
    135    * The purpose of userhash is to avoid keeping the password in cleartext
    136    * on the server side.
    137    */
    138   uint8_t userdigest_sha256[MHD_SHA256_DIGEST_SIZE];
    139 
    140   /**
    141    * The SHA-512/256 hash of the username together with the realm.
    142    * This hash can be used by the client to send the username in encrypted
    143    * form.
    144    * The purpose of userhash is to hide user identity when transmitting
    145    * requests over insecure link.
    146    */
    147   uint8_t userhash_sha512_256[MHD_SHA512_256_DIGEST_SIZE];
    148   /**
    149    * The SHA-512/256 hash of the username with the password and the realm.
    150    * It is used to verify that password used by the client matches password
    151    * required by the server.
    152    * The purpose of userhash is to avoid keeping the password in cleartext
    153    * on the server side.
    154    */
    155   uint8_t userdigest_sha512_256[MHD_SHA512_256_DIGEST_SIZE];
    156 
    157   /**
    158    * User has access to "area 1" if non-zero
    159    */
    160   int allow_area_1;
    161 
    162   /**
    163    * User has access to "area 2" if non-zero
    164    */
    165   int allow_area_2;
    166 };
    167 
    168 /**
    169  * The array of user entries.
    170  * In real application it should be loaded from external sources
    171  * at the application startup.
    172  */
    173 static struct UserEntry user_ids[2];
    174 
    175 /**
    176  * The number of entries used in @a user_ids.
    177  */
    178 static size_t user_ids_used = 0;
    179 
    180 /**
    181  * Add new user to the users database/array.
    182  *
    183  * This kind of function must be used only when the new user is introduced.
    184  * It must not be used at the every start of the application. The database
    185  * of users should be stored somewhere and reloaded when application is
    186  * started.
    187  *
    188  * @param username the username of the new user
    189  * @param password the password of the new user
    190  * @param realm the realm (the protection space) for which the new user
    191  *              is added
    192  * @param allow_area_1 if non-zero than user has access to the "area 1"
    193  * @param allow_area_2 if non-zero than user has access to the "area 2"
    194  * @return non-zero on success,
    195  *         zero on failure (like no more space in the database).
    196  */
    197 static int
    198 add_new_user_entry (const char *const username,
    199                     const char *const password,
    200                     const char *const realm,
    201                     int allow_area_1,
    202                     int allow_area_2)
    203 {
    204   struct UserEntry *entry;
    205   enum MHD_Result res;
    206 
    207   if ((sizeof(user_ids) / sizeof(user_ids[0])) <= user_ids_used)
    208     return 0; /* No more space to add new entry */
    209 
    210   entry = user_ids + user_ids_used;
    211 
    212   entry->username = username;
    213   entry->realm = realm;
    214 
    215   res = MHD_YES;
    216 
    217   if (MHD_NO != res)
    218     res = MHD_digest_auth_calc_userhash (MHD_DIGEST_AUTH_ALGO3_MD5,
    219                                          username,
    220                                          realm,
    221                                          entry->userhash_md5,
    222                                          sizeof(entry->userhash_md5));
    223   if (MHD_NO != res)
    224     res = MHD_digest_auth_calc_userdigest (MHD_DIGEST_AUTH_ALGO3_MD5,
    225                                            username,
    226                                            realm,
    227                                            password,
    228                                            entry->userdigest_md5,
    229                                            sizeof(entry->userdigest_md5));
    230 
    231   if (MHD_NO != res)
    232     res = MHD_digest_auth_calc_userhash (MHD_DIGEST_AUTH_ALGO3_SHA256,
    233                                          username,
    234                                          realm,
    235                                          entry->userhash_sha256,
    236                                          sizeof(entry->userhash_sha256));
    237   if (MHD_NO != res)
    238     res = MHD_digest_auth_calc_userdigest (MHD_DIGEST_AUTH_ALGO3_SHA256,
    239                                            username,
    240                                            realm,
    241                                            password,
    242                                            entry->userdigest_sha256,
    243                                            sizeof(entry->userdigest_sha256));
    244 
    245   if (MHD_NO != res)
    246     res = MHD_digest_auth_calc_userhash (MHD_DIGEST_AUTH_ALGO3_SHA512_256,
    247                                          username,
    248                                          realm,
    249                                          entry->userhash_sha512_256,
    250                                          sizeof(entry->userhash_sha512_256));
    251   if (MHD_NO != res)
    252     res =
    253       MHD_digest_auth_calc_userdigest (MHD_DIGEST_AUTH_ALGO3_SHA512_256,
    254                                        username,
    255                                        realm,
    256                                        password,
    257                                        entry->userdigest_sha512_256,
    258                                        sizeof(entry->userdigest_sha512_256));
    259 
    260   if (MHD_NO == res)
    261     return 0; /* Failure exit point */
    262 
    263   entry->allow_area_1 = allow_area_1;
    264   entry->allow_area_2 = allow_area_2;
    265 
    266   user_ids_used++;
    267 
    268   return ! 0;
    269 }
    270 
    271 
    272 /**
    273  * Find the user entry for specified username
    274  * @param username the username to find
    275  * @return NULL if no entry for specified username is found,
    276  *         pointer to user entry if found
    277  */
    278 static struct UserEntry *
    279 find_entry_by_username (const char *const username)
    280 {
    281   size_t i;
    282 
    283   for (i = 0; i < (sizeof(user_ids) / sizeof(user_ids[0])); ++i)
    284   {
    285     struct UserEntry *entry;
    286 
    287     entry = user_ids + i;
    288     if (0 == strcmp (username, entry->username))
    289       return entry;
    290   }
    291   return NULL;
    292 }
    293 
    294 
    295 /**
    296  * Find the user entry for specified userhash
    297  * @param algo3 the algorithm used for userhash calculation
    298  * @param userhash the userhash identifier to find
    299  * @param userhash_size the size @a userhash in bytes
    300  * @return NULL if no entry for specified userhash is found,
    301  *         pointer to user entry if found
    302  */
    303 static struct UserEntry *
    304 find_entry_by_userhash (enum MHD_DigestAuthAlgo3 algo3,
    305                         const void *userhash,
    306                         size_t userhash_size)
    307 {
    308   size_t i;
    309 
    310   if (MHD_digest_get_hash_size (algo3) != userhash_size)
    311     return NULL; /* Wrong length of the userhash */
    312 
    313   switch (algo3)
    314   {
    315   case MHD_DIGEST_AUTH_ALGO3_MD5:
    316   case MHD_DIGEST_AUTH_ALGO3_MD5_SESSION: /* An extra case not used currently */
    317     if (sizeof(user_ids[0].userhash_md5) != userhash_size) /* Extra check. The size was checked before */
    318       return NULL;
    319     for (i = 0; i < (sizeof(user_ids) / sizeof(user_ids[0])); ++i)
    320     {
    321       struct UserEntry *entry;
    322 
    323       entry = user_ids + i;
    324       if (0 == memcmp (userhash, entry->userhash_md5,
    325                        sizeof(entry->userhash_md5)))
    326         return entry;
    327     }
    328     break;
    329   case MHD_DIGEST_AUTH_ALGO3_SHA256:
    330   case MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION: /* An extra case not used currently */
    331     if (sizeof(user_ids[0].userhash_sha256) != userhash_size) /* Extra check. The size was checked before */
    332       return NULL;
    333     for (i = 0; i < (sizeof(user_ids) / sizeof(user_ids[0])); ++i)
    334     {
    335       struct UserEntry *entry;
    336 
    337       entry = user_ids + i;
    338       if (0 == memcmp (userhash, entry->userhash_sha256,
    339                        sizeof(entry->userhash_sha256)))
    340         return entry;
    341     }
    342     break;
    343   case MHD_DIGEST_AUTH_ALGO3_SHA512_256:
    344   case MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION: /* An extra case not used currently */
    345     if (sizeof(user_ids[0].userhash_sha512_256) != userhash_size) /* Extra check. The size was checked before */
    346       return NULL;
    347     for (i = 0; i < (sizeof(user_ids) / sizeof(user_ids[0])); ++i)
    348     {
    349       struct UserEntry *entry;
    350 
    351       entry = user_ids + i;
    352       if (0 == memcmp (userhash, entry->userhash_sha512_256,
    353                        sizeof(entry->userhash_sha512_256)))
    354         return entry;
    355     }
    356     break;
    357   case MHD_DIGEST_AUTH_ALGO3_INVALID: /* Mute compiler warning. Impossible value in this context. */
    358   default:
    359     break;
    360   }
    361   return NULL;
    362 }
    363 
    364 
    365 /**
    366  * Find the user entry for the user specified by provided username info
    367  * @param user_info the pointer to the structure username info returned by MHD
    368  * @return NULL if no entry for specified username info is found,
    369  *         pointer to user entry if found
    370  */
    371 static struct UserEntry *
    372 find_entry_by_userinfo (const struct MHD_DigestAuthUsernameInfo *username_info)
    373 {
    374   if (MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD <= username_info->uname_type)
    375     return find_entry_by_username (username_info->username);
    376 
    377   if (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == username_info->uname_type)
    378     return find_entry_by_userhash (username_info->algo3,
    379                                    username_info->userhash_bin,
    380                                    username_info->userhash_hex_len / 2);
    381 
    382   return NULL; /* Should be unreachable as all cases are covered before */
    383 }
    384 
    385 
    386 /* *** End of "database" of users and "database" functions *** */
    387 
    388 /* *** Requests handling *** */
    389 
    390 /**
    391  * Send "Requested HTTP method is not supported" page
    392  * @param c the connection structure
    393  * @return MHD_YES if response was successfully queued,
    394  *         MHD_NO otherwise
    395  */
    396 static enum MHD_Result
    397 reply_with_page_not_found (struct MHD_Connection *c)
    398 {
    399   static const char page_content[] =
    400     "<html><head><title>Page Not Found</title></head>" \
    401     "<body>The requested page not found.</body></html>";
    402   static const size_t page_content_len =
    403     (sizeof(page_content) / sizeof(char)) - 1;
    404   struct MHD_Response *resp;
    405   enum MHD_Result ret;
    406 
    407   resp = MHD_create_response_from_buffer_static (page_content_len,
    408                                                  page_content);
    409   if (NULL == resp)
    410     return MHD_NO;
    411 
    412   /* Ignore possible error when adding the header as the reply will work even
    413      without this header. */
    414   (void) MHD_add_response_header (resp,
    415                                   MHD_HTTP_HEADER_CONTENT_TYPE,
    416                                   "text/html");
    417 
    418   ret = MHD_queue_response (c, MHD_HTTP_NOT_FOUND, resp);
    419   MHD_destroy_response (resp);
    420   return ret;
    421 }
    422 
    423 
    424 /**
    425  * Get enum MHD_DigestAuthMultiAlgo3 value to be used for authentication.
    426  * @return the algorithm number/value
    427  */
    428 static enum MHD_DigestAuthMultiAlgo3
    429 get_m_algo (void)
    430 {
    431   if (force_md5)
    432     return MHD_DIGEST_AUTH_MULT_ALGO3_MD5;
    433   else if (force_sha256)
    434     return MHD_DIGEST_AUTH_MULT_ALGO3_SHA256;
    435   else if (force_sha512_256)
    436     return MHD_DIGEST_AUTH_MULT_ALGO3_SHA512_256;
    437 
    438   /* No forced algorithm selection, let MHD to use default */
    439   return MHD_DIGEST_AUTH_MULT_ALGO3_ANY_NON_SESSION;
    440 }
    441 
    442 
    443 /**
    444  * Get enum MHD_DigestAuthMultiQOP value to be used for authentication.
    445  * @return the "Quality Of Protection" number/value
    446  */
    447 static enum MHD_DigestAuthMultiQOP
    448 get_m_QOP (void)
    449 {
    450   if (allow_rfc2069)
    451     return MHD_DIGEST_AUTH_MULT_QOP_ANY_NON_INT;
    452 
    453   return MHD_DIGEST_AUTH_MULT_QOP_AUTH;
    454 }
    455 
    456 
    457 /**
    458  * Send "Authentication required" page
    459  * @param c the connection structure
    460  * @param stale if non-zero then "nonce stale" is indicated in the reply
    461  * @param wrong_cred if non-zero then client is informed the previously
    462  *                   it used wrong credentials
    463  * @return MHD_YES if response was successfully queued,
    464  *         MHD_NO otherwise
    465  */
    466 static enum MHD_Result
    467 reply_with_auth_required (struct MHD_Connection *c,
    468                           int stale,
    469                           int wrong_cred)
    470 {
    471   static const char auth_required_content[] =
    472     "<html><head><title>Authentication required</title></head>" \
    473     "<body>The requested page needs authentication.</body></html>";
    474   static const size_t auth_required_content_len =
    475     (sizeof(auth_required_content) / sizeof(char)) - 1;
    476   static const char wrong_creds_content[] =
    477     "<html><head><title>Wrong credentials</title></head>" \
    478     "<body>The provided credentials are incorrect.</body></html>";
    479   static const size_t wrong_creds_content_len =
    480     (sizeof(wrong_creds_content) / sizeof(char)) - 1;
    481   struct MHD_Response *resp;
    482   enum MHD_Result ret;
    483 
    484   if (wrong_cred)
    485     stale = 0; /* Force client to ask user for username and password */
    486 
    487   if (! wrong_cred)
    488     resp = MHD_create_response_from_buffer_static (auth_required_content_len,
    489                                                    auth_required_content);
    490   else
    491     resp = MHD_create_response_from_buffer_static (wrong_creds_content_len,
    492                                                    wrong_creds_content);
    493   if (NULL == resp)
    494     return MHD_NO;
    495 
    496   /* Ignore possible error when adding the header as the reply will work even
    497      without this header. */
    498   (void) MHD_add_response_header (resp,
    499                                   MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
    500 
    501 
    502   ret = MHD_queue_auth_required_response3 (
    503     c,
    504     REALM,
    505     OPAQUE_DATA, /* The "opaque data", not really useful */
    506     SEC_AREA1_URL " " SEC_AREA2_URL, /* Space-separated list of URLs' initial parts */
    507     resp,
    508     stale,
    509     get_m_QOP (),
    510     get_m_algo (),
    511     ! 0, /* Userhash support enabled */
    512     ! 0 /* UTF-8 is preferred */);
    513   MHD_destroy_response (resp);
    514   return ret;
    515 }
    516 
    517 
    518 /**
    519  * Send "Forbidden" page
    520  * @param c the connection structure
    521  * @return MHD_YES if response was successfully queued,
    522  *         MHD_NO otherwise
    523  */
    524 static enum MHD_Result
    525 reply_with_forbidden (struct MHD_Connection *c)
    526 {
    527   static const char page_content[] =
    528     "<html><head><title>Forbidden</title></head>" \
    529     "<body>You do not have access to this page.</body></html>";
    530   static const size_t page_content_len =
    531     (sizeof(page_content) / sizeof(char)) - 1;
    532   struct MHD_Response *resp;
    533   enum MHD_Result ret;
    534 
    535   resp = MHD_create_response_from_buffer_static (page_content_len, page_content)
    536   ;
    537   if (NULL == resp)
    538     return MHD_NO;
    539 
    540   /* Ignore possible error when adding the header as the reply will work even
    541      without this header. */
    542   (void) MHD_add_response_header (resp,
    543                                   MHD_HTTP_HEADER_CONTENT_TYPE,
    544                                   "text/html");
    545 
    546   ret = MHD_queue_response (c, MHD_HTTP_FORBIDDEN, resp);
    547   MHD_destroy_response (resp);
    548   return ret;
    549 }
    550 
    551 
    552 /**
    553  * Send "Area 1" pages
    554  * @param c the connection structure
    555  * @param url the requested URL
    556  * @return MHD_YES if response was successfully queued,
    557  *         MHD_NO otherwise
    558  */
    559 static enum MHD_Result
    560 reply_with_area1_pages (struct MHD_Connection *c,
    561                         const char *url)
    562 {
    563 
    564   if (0 == strcmp (url, SEC_AREA1_URL ""))
    565   {
    566     static const char page_content[] =
    567       "<html><head><title>Restricted secret page</title></head>" \
    568       "<body>Welcome to the restricted area</body></html>";
    569     static const size_t page_content_len =
    570       (sizeof(page_content) / sizeof(char)) - 1;
    571     struct MHD_Response *resp;
    572     enum MHD_Result ret;
    573 
    574     resp = MHD_create_response_from_buffer_static (page_content_len,
    575                                                    page_content);
    576     if (NULL == resp)
    577       return MHD_NO;
    578 
    579     /* Ignore possible error when adding the header as the reply will work even
    580        without this header. */
    581     (void) MHD_add_response_header (resp, MHD_HTTP_HEADER_CONTENT_TYPE,
    582                                     "text/html");
    583 
    584     ret = MHD_queue_response (c, MHD_HTTP_OK, resp);
    585     MHD_destroy_response (resp);
    586     return ret;
    587   }
    588   /* If needed: add handlers for other URLs in this area */
    589 #if 0 /* Disabled code */
    590   if (0 == strcmp (url, SEC_AREA1_URL "some_path/some_page"))
    591   {
    592     /* Add page creation/processing code */
    593   }
    594 #endif /* Disabled code */
    595 
    596   /* The requested URL is unknown */
    597   return reply_with_page_not_found (c);
    598 }
    599 
    600 
    601 /**
    602  * Send "Area 2" pages
    603  * @param c the connection structure
    604  * @param url the requested URL
    605  * @return MHD_YES if response was successfully queued,
    606  *         MHD_NO otherwise
    607  */
    608 static enum MHD_Result
    609 reply_with_area2_pages (struct MHD_Connection *c,
    610                         const char *url)
    611 {
    612 
    613   if (0 == strcmp (url, SEC_AREA2_URL ""))
    614   {
    615     static const char page_content[] =
    616       "<html><head><title>Very restricted secret page</title></head>" \
    617       "<body>Welcome to the super restricted area</body></html>";
    618     static const size_t page_content_len =
    619       (sizeof(page_content) / sizeof(char)) - 1;
    620     struct MHD_Response *resp;
    621     enum MHD_Result ret;
    622 
    623     resp = MHD_create_response_from_buffer_static (page_content_len,
    624                                                    page_content);
    625     if (NULL == resp)
    626       return MHD_NO;
    627 
    628     /* Ignore possible error when adding the header as the reply will work even
    629        without this header. */
    630     (void) MHD_add_response_header (resp, MHD_HTTP_HEADER_CONTENT_TYPE,
    631                                     "text/html");
    632 
    633     ret = MHD_queue_response (c, MHD_HTTP_OK, resp);
    634     MHD_destroy_response (resp);
    635     return ret;
    636   }
    637   /* If needed: add handlers for other URLs in this area */
    638 #if 0 /* Disabled code */
    639   if (0 == strcmp (url, SEC_AREA2_URL "other_path/other_page"))
    640   {
    641     /* Add page creation/processing code */
    642   }
    643 #endif /* Disabled code */
    644 
    645   /* The requested URL is unknown */
    646   return reply_with_page_not_found (c);
    647 }
    648 
    649 
    650 /**
    651  * Handle client's request for secured areas
    652  * @param c the connection structure
    653  * @param url the URL requested by the client
    654  * @param sec_area_num the number of secured area
    655  * @return MHD_YES if request was handled (either with "denied" or with
    656  *                 "allowed" result),
    657  *         MHD_NO if it was an error handling the request.
    658  */
    659 static enum MHD_Result
    660 handle_sec_areas_req (struct MHD_Connection *c, const char *url, unsigned int
    661                       sec_area_num)
    662 {
    663   struct MHD_DigestAuthUsernameInfo *username_info;
    664   struct UserEntry *user_entry;
    665   void *userdigest;
    666   size_t userdigest_size;
    667   enum MHD_DigestAuthResult auth_res;
    668 
    669   username_info = MHD_digest_auth_get_username3 (c);
    670 
    671   if (NULL == username_info)
    672     return reply_with_auth_required (c, 0, 0);
    673 
    674   user_entry = find_entry_by_userinfo (username_info);
    675 
    676   if (NULL == user_entry)
    677     return reply_with_auth_required (c, 0, 1);
    678 
    679   switch (username_info->algo3)
    680   {
    681   case MHD_DIGEST_AUTH_ALGO3_MD5:
    682     userdigest = user_entry->userdigest_md5;
    683     userdigest_size = sizeof(user_entry->userdigest_md5);
    684     break;
    685   case MHD_DIGEST_AUTH_ALGO3_SHA256:
    686     userdigest = user_entry->userdigest_sha256;
    687     userdigest_size = sizeof(user_entry->userdigest_sha256);
    688     break;
    689   case MHD_DIGEST_AUTH_ALGO3_SHA512_256:
    690     userdigest = user_entry->userdigest_sha512_256;
    691     userdigest_size = sizeof(user_entry->userdigest_sha512_256);
    692     break;
    693   case MHD_DIGEST_AUTH_ALGO3_MD5_SESSION:
    694   case MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION:
    695   case MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION:
    696     /* Not supported currently and not used by MHD.
    697        The client incorrectly used algorithm not advertised by the server. */
    698     return reply_with_auth_required (c, 0, 1);
    699   case MHD_DIGEST_AUTH_ALGO3_INVALID: /* Mute compiler warning */
    700   default:
    701     return MHD_NO; /* Should be unreachable */
    702   }
    703 
    704   auth_res = MHD_digest_auth_check_digest3 (
    705     c,
    706     REALM, /* Make sure to use the proper realm, not the realm provided by the client and returned by "user_entry" */
    707     user_entry->username,
    708     userdigest,
    709     userdigest_size,
    710     0, /* Use daemon's default value for nonce_timeout*/
    711     0, /* Use daemon's default value for max_nc */
    712     get_m_QOP (),
    713     (enum MHD_DigestAuthMultiAlgo3) username_info->algo3 /* Direct cast from "single algorithm" to "multi-algorithm" is allowed */
    714     );
    715 
    716   if (MHD_DAUTH_OK != auth_res)
    717   {
    718     int need_just_refresh_nonce;
    719     /* Actually MHD_DAUTH_NONCE_OTHER_COND should not be returned as
    720        MHD_OPTION_DIGEST_AUTH_NONCE_BIND_TYPE is not used for the daemon.
    721        To keep the code universal the MHD_DAUTH_NONCE_OTHER_COND is
    722        still checked here. */
    723     need_just_refresh_nonce =
    724       (MHD_DAUTH_NONCE_STALE == auth_res)
    725       || (MHD_DAUTH_NONCE_OTHER_COND == auth_res);
    726     return reply_with_auth_required (c,
    727                                      need_just_refresh_nonce,
    728                                      ! need_just_refresh_nonce);
    729   }
    730 
    731   /* The user successfully authenticated */
    732 
    733   /* Check whether access to the request area is allowed for the user */
    734   if (1 == sec_area_num)
    735   {
    736     if (user_entry->allow_area_1)
    737       return reply_with_area1_pages (c, url);
    738     else
    739       return reply_with_forbidden (c);
    740   }
    741   else if (2 == sec_area_num)
    742   {
    743     if (user_entry->allow_area_2)
    744       return reply_with_area2_pages (c, url);
    745     else
    746       return reply_with_forbidden (c);
    747   }
    748 
    749   return MHD_NO; /* Should be unreachable */
    750 }
    751 
    752 
    753 /**
    754  * Send the main page
    755  * @param c the connection structure
    756  * @return MHD_YES if response was successfully queued,
    757  *         MHD_NO otherwise
    758  */
    759 static enum MHD_Result
    760 reply_with_main_page (struct MHD_Connection *c)
    761 {
    762   static const char page_content[] = MAIN_PAGE;
    763   static const size_t page_content_len =
    764     (sizeof(page_content) / sizeof(char)) - 1;
    765   struct MHD_Response *resp;
    766   enum MHD_Result ret;
    767 
    768   resp = MHD_create_response_from_buffer_static (page_content_len, page_content)
    769   ;
    770   if (NULL == resp)
    771     return MHD_NO;
    772 
    773   /* Ignore possible error when adding the header as the reply will work even
    774      without this header. */
    775   (void) MHD_add_response_header (resp,
    776                                   MHD_HTTP_HEADER_CONTENT_TYPE,
    777                                   "text/html");
    778 
    779   ret = MHD_queue_response (c, MHD_HTTP_OK, resp);
    780   MHD_destroy_response (resp);
    781   return ret;
    782 }
    783 
    784 
    785 /**
    786  * Send "Requested HTTP method is not supported" page
    787  * @param c the connection structure
    788  * @return MHD_YES if response was successfully queued,
    789  *         MHD_NO otherwise
    790  */
    791 static enum MHD_Result
    792 reply_with_method_not_supported (struct MHD_Connection *c)
    793 {
    794   static const char page_content[] =
    795     "<html><head><title>Requested HTTP Method Is Not Supported</title></head>" \
    796     "<body>The requested HTTP method is not supported.</body></html>";
    797   static const size_t page_content_len =
    798     (sizeof(page_content) / sizeof(char)) - 1;
    799   struct MHD_Response *resp;
    800   enum MHD_Result ret;
    801 
    802   resp = MHD_create_response_from_buffer_static (page_content_len, page_content)
    803   ;
    804   if (NULL == resp)
    805     return MHD_NO;
    806 
    807   /* Ignore possible error when adding the header as the reply will work even
    808      without this header. */
    809   (void) MHD_add_response_header (resp,
    810                                   MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
    811 
    812   ret = MHD_queue_response (c, MHD_HTTP_NOT_IMPLEMENTED, resp);
    813   MHD_destroy_response (resp);
    814   return ret;
    815 }
    816 
    817 
    818 static enum MHD_Result
    819 ahc_main (void *cls,
    820           struct MHD_Connection *connection,
    821           const char *url,
    822           const char *method,
    823           const char *version,
    824           const char *upload_data, size_t *upload_data_size,
    825           void **req_cls)
    826 {
    827   static int already_called_marker;
    828   size_t url_len;
    829   (void) cls;               /* Unused. Silent compiler warning. */
    830   (void) version;           /* Unused. Silent compiler warning. */
    831   (void) upload_data;       /* Unused. Silent compiler warning. */
    832 
    833   if ((0 != strcmp (method, MHD_HTTP_METHOD_GET))
    834       && (0 != strcmp (method, MHD_HTTP_METHOD_HEAD)))
    835     return reply_with_method_not_supported (connection);
    836 
    837   if (0 != *upload_data_size)
    838     return MHD_NO; /* No upload expected for GET or HEAD */
    839 
    840   if (&already_called_marker != *req_cls)
    841   { /* Called for the first time, request not fully read yet */
    842     *req_cls = &already_called_marker;
    843     /* Wait for complete request */
    844     return MHD_YES;
    845   }
    846 
    847   if (0 == strcmp (url, "/"))
    848     return reply_with_main_page (connection);
    849 
    850   url_len = strlen (url);
    851 
    852   if ((strlen (SEC_AREA1_URL) <= url_len)
    853       && (0 == memcmp (url, SEC_AREA1_URL, strlen (SEC_AREA1_URL))))
    854     return handle_sec_areas_req (connection, url, 1); /* The requested URL is within SEC_AREA1_URL */
    855 
    856   if ((strlen (SEC_AREA2_URL) <= url_len)
    857       && (0 == memcmp (url, SEC_AREA2_URL, strlen (SEC_AREA2_URL))))
    858     return handle_sec_areas_req (connection, url, 2); /* The requested URL is within SEC_AREA2_URL */
    859 
    860   return reply_with_page_not_found (connection);
    861 }
    862 
    863 
    864 /* *** End of requests handling *** */
    865 
    866 
    867 /**
    868  * Add new users to the users "database".
    869  *
    870  * In real application this kind of function must NOT be called at
    871  * the application startup. Instead similar function should be
    872  * called only when new user is introduced. The users "database"
    873  * should be stored somewhere and reloaded at the application
    874  * startup.
    875  *
    876  * @return non-zero on success,
    877  *         zero in case of error.
    878  */
    879 static int
    880 add_new_users (void)
    881 {
    882   if (! add_new_user_entry ("joepublic",
    883                             "password",
    884                             REALM,
    885                             ! 0,
    886                             0))
    887     return 0;
    888 
    889   if (! add_new_user_entry ("superadmin",
    890                             "pA$$w0Rd",
    891                             REALM,
    892                             ! 0,
    893                             ! 0))
    894     return 0;
    895 
    896   return ! 0;
    897 }
    898 
    899 
    900 /**
    901  * Check and apply application parameters
    902  * @param argc the argc of the @a main function
    903  * @param argv the argv of the @a main function
    904  * @return non-zero on success,
    905  *         zero in case of any error (like wrong parameters).
    906  */
    907 static int
    908 check_params (int argc, char *const *const argv)
    909 {
    910   size_t i;
    911   unsigned int port_value;
    912 
    913   if (2 > argc)
    914     return 0;
    915 
    916   for (i = 1; i < (unsigned int) argc; ++i)
    917   {
    918     if (0 == strcmp (argv[i], "--md5"))
    919     { /* Force use MD5 */
    920       force_md5 = ! 0;
    921       force_sha256 = 0;
    922       force_sha512_256 = 0;
    923     }
    924     else if (0 == strcmp (argv[i], "--sha256"))
    925     { /* Force use SHA-256 instead of default MD5 */
    926       force_md5 = 0;
    927       force_sha256 = ! 0;
    928       force_sha512_256 = 0;
    929     }
    930     else if (0 == strcmp (argv[i], "--sha512-256"))
    931     { /* Force use SHA-512/256 instead of default MD5 */
    932       force_md5 = 0;
    933       force_sha256 = 0;
    934       force_sha512_256 = ! 0;
    935     }
    936     else if (0 == strcmp (argv[i], "--allow-rfc2069"))
    937       allow_rfc2069 = ! 0; /* Allow fallback to RFC2069. Not recommended! */
    938     else if ((1 == sscanf (argv[i], "%u", &port_value))
    939              && (0 < port_value) && (65535 >= port_value))
    940       daemon_port = (uint16_t) port_value;
    941     else
    942     {
    943       fprintf (stderr, "Unrecognized parameter: %s\n",
    944                argv[i]);
    945       return 0;
    946     }
    947   }
    948 
    949   if (force_sha512_256)
    950     printf (
    951       "Note: when testing with curl/libcurl do not be surprised with failures as "
    952       "libcurl incorrectly implements SHA-512/256 algorithm.\n");
    953   return ! 0;
    954 }
    955 
    956 
    957 /**
    958  * The cryptographically secure random data
    959  */
    960 static uint8_t rand_data[8];
    961 
    962 /**
    963  * Initialise the random data
    964  * @return non-zero if succeed,
    965  *         zero if failed
    966  */
    967 static int
    968 init_rand_data (void)
    969 {
    970 #if ! defined(_WIN32) || defined(__CYGWIN__)
    971   int fd;
    972   ssize_t len;
    973   size_t off;
    974 
    975   fd = open ("/dev/urandom", O_RDONLY);
    976   if (-1 == fd)
    977   {
    978     fprintf (stderr, "Failed to open '%s': %s\n",
    979              "/dev/urandom",
    980              strerror (errno));
    981     return 0;
    982   }
    983   for (off = 0; off < sizeof(rand_data); off += (size_t) len)
    984   {
    985     len = read (fd, rand_data, 8);
    986     if (0 > len)
    987     {
    988       fprintf (stderr, "Failed to read '%s': %s\n",
    989                "/dev/urandom",
    990                strerror (errno));
    991       (void) close (fd);
    992       return 0;
    993     }
    994   }
    995   (void) close (fd);
    996 #else  /* Native W32 */
    997   HCRYPTPROV cc;
    998   BOOL b;
    999 
   1000   b = CryptAcquireContext (&cc,
   1001                            NULL,
   1002                            NULL,
   1003                            PROV_RSA_FULL,
   1004                            CRYPT_VERIFYCONTEXT);
   1005   if (FALSE == b)
   1006   {
   1007     fprintf (stderr,
   1008              "Failed to acquire crypto provider context: %lu\n",
   1009              (unsigned long) GetLastError ());
   1010     return 0;
   1011   }
   1012   b = CryptGenRandom (cc, sizeof(rand_data), (BYTE *) rand_data);
   1013   if (FALSE == b)
   1014   {
   1015     fprintf (stderr,
   1016              "Failed to generate 8 random bytes: %lu\n",
   1017              GetLastError ());
   1018   }
   1019   CryptReleaseContext (cc, 0);
   1020   if (FALSE == b)
   1021     return 0;
   1022 #endif /* Native W32 */
   1023 
   1024   return ! 0;
   1025 }
   1026 
   1027 
   1028 int
   1029 main (int argc, char *const *argv)
   1030 {
   1031   struct MHD_Daemon *d;
   1032 
   1033   if (! check_params (argc, argv))
   1034   {
   1035     fprintf (stderr, "Usage: %s [--md5|--sha256|--sha512-256] "
   1036              "[--allow-rfc2069] PORT\n", argv[0]);
   1037     return 1;
   1038   }
   1039   if (! add_new_users ())
   1040   {
   1041     fprintf (stderr, "Failed to add new users to the users database.\n");
   1042     return 2;
   1043   }
   1044   if (! init_rand_data ())
   1045   {
   1046     fprintf (stderr, "Failed to initialise random data.\n");
   1047     return 2;
   1048   }
   1049 
   1050   d = MHD_start_daemon (
   1051     MHD_USE_INTERNAL_POLLING_THREAD
   1052     | MHD_USE_THREAD_PER_CONNECTION
   1053     | MHD_USE_ERROR_LOG,
   1054     daemon_port,
   1055     NULL, NULL, &ahc_main, NULL,
   1056     MHD_OPTION_DIGEST_AUTH_RANDOM, sizeof(rand_data), rand_data,
   1057     MHD_OPTION_NONCE_NC_SIZE, 500,
   1058     MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 180,
   1059     MHD_OPTION_END);
   1060   if (d == NULL)
   1061   {
   1062     fprintf (stderr, "Failed to start the server on port %lu.\n",
   1063              (unsigned long) daemon_port);
   1064     return 1;
   1065   }
   1066   printf ("Running server on port %lu.\nPress ENTER to stop.\n",
   1067           (unsigned long) daemon_port);
   1068   (void) getc (stdin);
   1069   MHD_stop_daemon (d);
   1070   return 0;
   1071 }
   1072 
   1073 
   1074 /* End of digest_auth_example_adv.c */