minimal_auth_digest.c (8324B)
1 /* SPDX-License-Identifier: 0BSD */ 2 /* 3 This file is part of GNU libmicrohttpd. 4 Copyright (C) 2024 Evgeny Grin (Karlson2k) 5 6 Permission to use, copy, modify, and/or distribute this software for 7 any purpose with or without fee is hereby granted. 8 9 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 10 WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 11 OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE 12 FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY 13 DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 14 AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 15 OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 /** 18 * @file minimal_auth_digest.c 19 * @brief Minimal example for Digest Authentication 20 * @author Karlson2k (Evgeny Grin) 21 */ 22 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <microhttpd2.h> 27 #if defined(_WIN32) && ! defined(__CYGWIN__) 28 # include <wincrypt.h> /* For entropy generation */ 29 #else 30 # include <sys/types.h> 31 # include <fcntl.h> /* open() function */ 32 # include <unistd.h> /* close() function */ 33 #endif /* _WIN32 && ! __CYGWIN__ */ 34 35 static MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_NONNULL_ (3) 36 const struct MHD_Action * 37 req_cb (void *cls, 38 struct MHD_Request *MHD_RESTRICT request, 39 const struct MHD_String *MHD_RESTRICT path, 40 enum MHD_HTTP_Method method, 41 uint_fast64_t upload_size) 42 { 43 static const char secret_page[] = "Welcome to the cave of treasures!\n"; 44 static const char auth_required_page[] = 45 "You need to know the secret to get in.\n"; 46 static const char msg_forbidden_page[] = 47 "You are not allowed to enter. Go away!\n"; 48 static const char msg_bad_header_page[] = 49 "The Authorization header data is invalid\n"; 50 static const char realm[] = "The secret cave"; 51 static const char allowed_username[] = "alibaba"; 52 static const char allowed_password[] = "open sesam"; 53 static const size_t allowed_username_len = 54 (sizeof(allowed_username) / sizeof(char) - 1); 55 union MHD_RequestInfoDynamicData req_data; 56 const struct MHD_AuthDigestInfo *uname; /* a shortcut */ 57 enum MHD_StatusCode res; 58 59 (void) cls; 60 (void) path; 61 (void) method; 62 (void) upload_size; /* Unused */ 63 64 res = 65 MHD_request_get_info_dynamic (request, 66 MHD_REQUEST_INFO_DYNAMIC_AUTH_DIGEST_INFO, 67 &req_data); 68 if (MHD_SC_AUTH_ABSENT == res) 69 return MHD_action_digest_auth_challenge_a ( 70 request, 71 realm, 72 "0", 73 NULL, 74 MHD_NO, 75 MHD_DIGEST_AUTH_MULT_QOP_AUTH, 76 MHD_DIGEST_AUTH_MULT_ALGO_ANY, 77 MHD_NO, 78 MHD_YES, 79 MHD_response_from_buffer_static ( 80 MHD_HTTP_STATUS_UNAUTHORIZED, 81 sizeof(auth_required_page) / sizeof(char) - 1, 82 auth_required_page)); 83 84 if (MHD_SC_REQ_AUTH_DATA_BROKEN == res) 85 return MHD_action_from_response ( 86 request, 87 MHD_response_from_buffer_static ( 88 MHD_HTTP_STATUS_BAD_REQUEST, 89 sizeof(msg_bad_header_page) / sizeof(char) - 1, 90 msg_bad_header_page)); 91 92 if (MHD_SC_OK != res) 93 return MHD_action_abort_request (request); 94 95 /* Assign result to short-named variable for convenience */ 96 uname = req_data.v_auth_digest_info; 97 if ((uname->username.len == allowed_username_len) && 98 (memcmp (allowed_username, 99 uname->username.cstr, 100 uname->username.len) == 0)) 101 { 102 /* The client gave the correct username. Check the password match. */ 103 enum MHD_DigestAuthResult auth_res; 104 105 auth_res = MHD_digest_auth_check (request, 106 realm, 107 allowed_username, 108 allowed_password, 109 0, 110 MHD_DIGEST_AUTH_MULT_QOP_AUTH, 111 MHD_DIGEST_AUTH_MULT_ALGO_ANY); 112 113 if (MHD_DAUTH_OK == auth_res) 114 /* User authenticated */ 115 return MHD_action_from_response ( 116 request, 117 MHD_response_from_buffer_static ( 118 MHD_HTTP_STATUS_OK, 119 sizeof(secret_page) / sizeof(char) - 1, 120 secret_page)); 121 122 if (MHD_DAUTH_NONCE_STALE == auth_res) 123 return MHD_action_digest_auth_challenge_a ( 124 request, 125 realm, 126 "0", 127 NULL, 128 MHD_YES /* Indicate "stale" nonce */, 129 MHD_DIGEST_AUTH_MULT_QOP_AUTH, 130 MHD_DIGEST_AUTH_MULT_ALGO_ANY, 131 MHD_NO, 132 MHD_YES, 133 MHD_response_from_buffer_static ( 134 MHD_HTTP_STATUS_UNAUTHORIZED, 135 sizeof(auth_required_page) / sizeof(char) - 1, 136 auth_required_page)); 137 138 if (MHD_DAUTH_NONCE_WRONG <= auth_res) 139 /* Wrong password or attack attempt */ 140 return MHD_action_from_response ( 141 request, 142 MHD_response_from_buffer_static ( 143 MHD_HTTP_STATUS_FORBIDDEN, 144 sizeof(msg_forbidden_page) / sizeof(char) - 1, 145 msg_forbidden_page)); 146 } 147 /* Wrong username */ 148 149 return MHD_action_from_response ( 150 request, 151 MHD_response_from_buffer_static ( 152 MHD_HTTP_STATUS_FORBIDDEN, 153 sizeof(msg_forbidden_page) / sizeof(char) - 1, 154 msg_forbidden_page)); 155 } 156 157 158 static char entropy_bytes[32]; 159 160 static int 161 init_entropy_bytes (void); 162 163 int 164 main (int argc, 165 char *const *argv) 166 { 167 struct MHD_Daemon *d; 168 int port; 169 170 if (argc != 2) 171 { 172 fprintf (stderr, 173 "Usage:\n%s PORT\n", 174 argv[0]); 175 return 1; 176 } 177 port = atoi (argv[1]); 178 if ((1 > port) || (65535 < port)) 179 { 180 fprintf (stderr, 181 "The PORT must be a numeric value between 1 and 65535.\n"); 182 return 2; 183 } 184 if (! init_entropy_bytes ()) 185 return 11; 186 187 d = MHD_daemon_create (&req_cb, 188 NULL); 189 if (NULL == d) 190 { 191 fprintf (stderr, 192 "Failed to create MHD daemon.\n"); 193 return 3; 194 } 195 if (MHD_SC_OK != 196 MHD_DAEMON_SET_OPTIONS ( 197 d, 198 MHD_D_OPTION_WM_WORKER_THREADS (1), 199 MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, 200 (uint_least16_t) port), 201 MHD_D_OPTION_RANDOM_ENTROPY (sizeof(entropy_bytes), 202 entropy_bytes))) 203 { 204 fprintf (stderr, 205 "Failed to set MHD daemon run parameters.\n"); 206 } 207 else 208 { 209 if (MHD_SC_OK != 210 MHD_daemon_start (d)) 211 { 212 fprintf (stderr, 213 "Failed to start MHD daemon.\n"); 214 } 215 else 216 { 217 printf ("The MHD daemon is listening on port %d\n" 218 "Press ENTER to stop.\n", port); 219 (void) fgetc (stdin); 220 } 221 } 222 printf ("Stopping... "); 223 fflush (stdout); 224 MHD_daemon_destroy (d); 225 printf ("OK\n"); 226 return 0; 227 } 228 229 230 /** 231 * Initialise random data 232 * @return non-zero if succeed, 233 * zero if failed 234 */ 235 static int 236 init_entropy_bytes (void) 237 { 238 #if ! defined(_WIN32) || defined(__CYGWIN__) 239 int fd; 240 ssize_t len; 241 size_t off; 242 243 fd = open ("/dev/urandom", O_RDONLY); 244 if (-1 == fd) 245 { 246 fd = open ("/dev/arandom", O_RDONLY); 247 if (-1 == fd) 248 fd = open ("/dev/random", O_RDONLY); 249 } 250 if (0 > fd) 251 { 252 fprintf (stderr, "Failed to open random data source.\n"); 253 return 0; 254 } 255 for (off = 0; off < sizeof (entropy_bytes); off += (size_t) len) 256 { 257 len = read (fd, 258 entropy_bytes + off, 259 sizeof (entropy_bytes) - off); 260 if (0 >= len) 261 { 262 fprintf (stderr, "Failed to read random data source.\n"); 263 (void) close (fd); 264 return 0; 265 } 266 } 267 (void) close (fd); 268 return ! 0; 269 #else /* Native W32 */ 270 HCRYPTPROV cc; 271 BOOL b; 272 273 b = CryptAcquireContext (&cc, 274 NULL, 275 NULL, 276 PROV_RSA_FULL, 277 CRYPT_VERIFYCONTEXT); 278 if (FALSE == b) 279 { 280 fprintf (stderr, 281 "Failed to acquire crypto provider context: %lu\n", 282 (unsigned long) GetLastError ()); 283 return 0; 284 } 285 b = CryptGenRandom (cc, sizeof(entropy_bytes), (BYTE *) entropy_bytes); 286 if (FALSE == b) 287 { 288 fprintf (stderr, 289 "Failed to generate random bytes: %lu\n", 290 GetLastError ()); 291 } 292 CryptReleaseContext (cc, 0); 293 return (FALSE != b); 294 #endif /* Native W32 */ 295 }