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