diff options
author | Evgeny Grin (Karlson2k) <k2k@narod.ru> | 2024-01-28 23:14:44 +0100 |
---|---|---|
committer | Evgeny Grin (Karlson2k) <k2k@narod.ru> | 2024-01-28 23:14:44 +0100 |
commit | bc827fcc09ac66b87a6ac052982beda393aeec60 (patch) | |
tree | 5dd6823f6d05cb5ed8924e96f86671f8418c70f9 | |
parent | f7969b87492f612dddad4d746725962f225b5de4 (diff) | |
download | libmicrohttpd-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.am | 8 | ||||
-rw-r--r-- | src/examples/digest_auth_example_adv.c | 1049 |
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 | ||
71 | if ENABLE_DAUTH | 71 | if ENABLE_DAUTH |
72 | noinst_PROGRAMS += \ | 72 | noinst_PROGRAMS += \ |
73 | digest_auth_example | 73 | digest_auth_example \ |
74 | digest_auth_example_adv | ||
74 | endif | 75 | endif |
75 | 76 | ||
76 | if ENABLE_BAUTH | 77 | if ENABLE_BAUTH |
@@ -215,6 +216,11 @@ digest_auth_example_SOURCES = \ | |||
215 | digest_auth_example_LDADD = \ | 216 | digest_auth_example_LDADD = \ |
216 | $(top_builddir)/src/microhttpd/libmicrohttpd.la | 217 | $(top_builddir)/src/microhttpd/libmicrohttpd.la |
217 | 218 | ||
219 | digest_auth_example_adv_SOURCES = \ | ||
220 | digest_auth_example_adv.c | ||
221 | digest_auth_example_adv_LDADD = \ | ||
222 | $(top_builddir)/src/microhttpd/libmicrohttpd.la | ||
223 | |||
218 | refuse_post_example_SOURCES = \ | 224 | refuse_post_example_SOURCES = \ |
219 | refuse_post_example.c | 225 | refuse_post_example.c |
220 | refuse_post_example_LDADD = \ | 226 | refuse_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 | */ | ||
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 | * User record. | ||
75 | * This kind of data (or something similar) should be stored in some database | ||
76 | * or file. | ||
77 | */ | ||
78 | struct 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 | */ | ||
170 | static struct UserEntry user_ids[2]; | ||
171 | |||
172 | /** | ||
173 | * The number of entries used in @a user_ids. | ||
174 | */ | ||
175 | static 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 | */ | ||
194 | static int | ||
195 | add_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 | */ | ||
275 | static struct UserEntry * | ||
276 | find_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 | */ | ||
300 | static struct UserEntry * | ||
301 | find_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 | */ | ||
368 | static struct UserEntry * | ||
369 | find_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 | */ | ||
389 | static enum MHD_Result | ||
390 | reply_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 | */ | ||
421 | static enum MHD_DigestAuthMultiAlgo3 | ||
422 | get_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 | */ | ||
439 | static enum MHD_DigestAuthMultiQOP | ||
440 | get_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 | */ | ||
458 | static enum MHD_Result | ||
459 | reply_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 | */ | ||
516 | static enum MHD_Result | ||
517 | reply_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 | */ | ||
551 | static enum MHD_Result | ||
552 | reply_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 | */ | ||
600 | static enum MHD_Result | ||
601 | reply_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 | */ | ||
651 | static enum MHD_Result | ||
652 | handle_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 | */ | ||
751 | static enum MHD_Result | ||
752 | reply_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 | */ | ||
783 | static enum MHD_Result | ||
784 | reply_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 | |||
810 | static enum MHD_Result | ||
811 | ahc_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 | */ | ||
868 | static int | ||
869 | add_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 | |||
889 | static int | ||
890 | check_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 | |||
939 | static uint8_t rand_data[8]; | ||
940 | |||
941 | /** | ||
942 | * Initialise random data | ||
943 | * @return non-zero if succeed, | ||
944 | * zero if failed | ||
945 | */ | ||
946 | static int | ||
947 | init_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 | |||
1007 | int | ||
1008 | main (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 */ | ||