]>
Commit | Line | Data |
---|---|---|
2d70df72 | 1 | /* |
262a0e14 | 2 | * $Id$ |
2d70df72 | 3 | * |
4 | * DEBUG: section 29 Authenticator | |
5 | * AUTHOR: Robert Collins | |
6 | * | |
7 | * SQUID Internet Object Cache http://squid.nlanr.net/Squid/ | |
8 | * ---------------------------------------------------------- | |
9 | * | |
10 | * Squid is the result of efforts by numerous individuals from the | |
11 | * Internet community. Development is led by Duane Wessels of the | |
12 | * National Laboratory for Applied Network Research and funded by the | |
13 | * National Science Foundation. Squid is Copyrighted (C) 1998 by | |
14 | * the Regents of the University of California. Please see the | |
15 | * COPYRIGHT file for full details. Squid incorporates software | |
16 | * developed and/or copyrighted by other sources. Please see the | |
17 | * CREDITS file for full details. | |
18 | * | |
19 | * This program is free software; you can redistribute it and/or modify | |
20 | * it under the terms of the GNU General Public License as published by | |
21 | * the Free Software Foundation; either version 2 of the License, or | |
22 | * (at your option) any later version. | |
26ac0430 | 23 | * |
2d70df72 | 24 | * This program is distributed in the hope that it will be useful, |
25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
27 | * GNU General Public License for more details. | |
26ac0430 | 28 | * |
2d70df72 | 29 | * You should have received a copy of the GNU General Public License |
30 | * along with this program; if not, write to the Free Software | |
31 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. | |
32 | * | |
33 | */ | |
34 | ||
35 | /* The functions in this file handle authentication. | |
36 | * They DO NOT perform access control or auditing. | |
37 | * See acl.c for access control and client_side.c for auditing */ | |
38 | ||
39 | ||
40 | #include "squid.h" | |
41 | #include "rfc2617.h" | |
42 | #include "auth_digest.h" | |
3ad63615 | 43 | #include "auth/Gadgets.h" |
a553a5a3 | 44 | #include "event.h" |
62ee09ca | 45 | #include "CacheManager.h" |
e6ccf245 | 46 | #include "Store.h" |
a2ac85d9 | 47 | #include "HttpRequest.h" |
924f73bc | 48 | #include "HttpReply.h" |
d295d770 | 49 | #include "wordlist.h" |
cc192b50 | 50 | #include "SquidTime.h" |
f5691f9c | 51 | /* TODO don't include this */ |
52 | #include "digestScheme.h" | |
c78aa667 | 53 | |
2d70df72 | 54 | /* Digest Scheme */ |
55 | ||
56 | static HLPCB authenticateDigestHandleReply; | |
2d70df72 | 57 | static AUTHSSTATS authenticateDigestStats; |
2d70df72 | 58 | |
59 | static helper *digestauthenticators = NULL; | |
60 | ||
61 | static hash_table *digest_nonce_cache; | |
62 | ||
f5691f9c | 63 | static AuthDigestConfig digestConfig; |
2d70df72 | 64 | |
65 | static int authdigest_initialised = 0; | |
a3efa961 | 66 | static MemAllocator *digest_nonce_pool = NULL; |
2d70df72 | 67 | |
e6ccf245 | 68 | CBDATA_TYPE(DigestAuthenticateStateData); |
2d70df72 | 69 | |
9abd1514 | 70 | enum http_digest_attr_type { |
d6b7a3c4 | 71 | DIGEST_USERNAME, |
cb14509d HN |
72 | DIGEST_REALM, |
73 | DIGEST_QOP, | |
74 | DIGEST_ALGORITHM, | |
75 | DIGEST_URI, | |
76 | DIGEST_NONCE, | |
77 | DIGEST_NC, | |
78 | DIGEST_CNONCE, | |
79 | DIGEST_RESPONSE, | |
80 | DIGEST_ENUM_END | |
81 | }; | |
82 | ||
83 | static const HttpHeaderFieldAttrs DigestAttrs[DIGEST_ENUM_END] = { | |
84 | {"username", (http_hdr_type)DIGEST_USERNAME}, | |
85 | {"realm", (http_hdr_type)DIGEST_REALM}, | |
86 | {"qop", (http_hdr_type)DIGEST_QOP}, | |
87 | {"algorithm", (http_hdr_type)DIGEST_ALGORITHM}, | |
88 | {"uri", (http_hdr_type)DIGEST_URI}, | |
89 | {"nonce", (http_hdr_type)DIGEST_NONCE}, | |
90 | {"nc", (http_hdr_type)DIGEST_NC}, | |
91 | {"cnonce", (http_hdr_type)DIGEST_CNONCE}, | |
92 | {"response", (http_hdr_type)DIGEST_RESPONSE}, | |
93 | }; | |
94 | ||
95 | static HttpHeaderFieldInfo *DigestFieldsInfo = NULL; | |
96 | ||
2d70df72 | 97 | /* |
98 | * | |
99 | * Nonce Functions | |
100 | * | |
101 | */ | |
102 | ||
103 | static void authenticateDigestNonceCacheCleanup(void *data); | |
104 | static digest_nonce_h *authenticateDigestNonceFindNonce(const char *nonceb64); | |
c193c972 | 105 | static digest_nonce_h *authenticateDigestNonceNew(void); |
c78aa667 | 106 | static void authenticateDigestNonceDelete(digest_nonce_h * nonce); |
c193c972 | 107 | static void authenticateDigestNonceSetup(void); |
108 | static void authenticateDigestNonceShutdown(void); | |
109 | static void authenticateDigestNonceReconfigure(void); | |
c78aa667 | 110 | static const char *authenticateDigestNonceNonceb64(digest_nonce_h * nonce); |
111 | static int authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9]); | |
112 | static int authDigestNonceIsStale(digest_nonce_h * nonce); | |
113 | static void authDigestNonceEncode(digest_nonce_h * nonce); | |
114 | static int authDigestNonceLastRequest(digest_nonce_h * nonce); | |
115 | static void authDigestNonceLink(digest_nonce_h * nonce); | |
116 | static void authDigestNonceUnlink(digest_nonce_h * nonce); | |
117 | #if NOT_USED | |
118 | static int authDigestNonceLinks(digest_nonce_h * nonce); | |
119 | #endif | |
120 | static void authDigestNonceUserUnlink(digest_nonce_h * nonce); | |
121 | static void authDigestNoncePurge(digest_nonce_h * nonce); | |
2d70df72 | 122 | |
c78aa667 | 123 | static void |
2d70df72 | 124 | authDigestNonceEncode(digest_nonce_h * nonce) |
125 | { | |
126 | if (!nonce) | |
62e76326 | 127 | return; |
128 | ||
4a8b20e8 | 129 | if (nonce->key) |
62e76326 | 130 | xfree(nonce->key); |
131 | ||
4a8b20e8 | 132 | nonce->key = xstrdup(base64_encode_bin((char *) &(nonce->noncedata), sizeof(digest_nonce_data))); |
2d70df72 | 133 | } |
134 | ||
c78aa667 | 135 | static digest_nonce_h * |
c193c972 | 136 | authenticateDigestNonceNew(void) |
2d70df72 | 137 | { |
b001e822 | 138 | digest_nonce_h *newnonce = static_cast < digest_nonce_h * >(digest_nonce_pool->alloc()); |
2d70df72 | 139 | digest_nonce_h *temp; |
140 | ||
62e76326 | 141 | /* NONCE CREATION - NOTES AND REASONING. RBC 20010108 |
142 | * === EXCERPT FROM RFC 2617 === | |
143 | * The contents of the nonce are implementation dependent. The quality | |
144 | * of the implementation depends on a good choice. A nonce might, for | |
145 | * example, be constructed as the base 64 encoding of | |
26ac0430 | 146 | * |
62e76326 | 147 | * time-stamp H(time-stamp ":" ETag ":" private-key) |
26ac0430 | 148 | * |
62e76326 | 149 | * where time-stamp is a server-generated time or other non-repeating |
150 | * value, ETag is the value of the HTTP ETag header associated with | |
151 | * the requested entity, and private-key is data known only to the | |
152 | * server. With a nonce of this form a server would recalculate the | |
153 | * hash portion after receiving the client authentication header and | |
154 | * reject the request if it did not match the nonce from that header | |
155 | * or if the time-stamp value is not recent enough. In this way the | |
156 | * server can limit the time of the nonce's validity. The inclusion of | |
157 | * the ETag prevents a replay request for an updated version of the | |
158 | * resource. (Note: including the IP address of the client in the | |
159 | * nonce would appear to offer the server the ability to limit the | |
160 | * reuse of the nonce to the same client that originally got it. | |
161 | * However, that would break proxy farms, where requests from a single | |
162 | * user often go through different proxies in the farm. Also, IP | |
163 | * address spoofing is not that hard.) | |
164 | * ==== | |
26ac0430 | 165 | * |
62e76326 | 166 | * Now for my reasoning: |
167 | * We will not accept a unrecognised nonce->we have all recognisable | |
82b045dc | 168 | * nonces stored. If we send out unique base64 encodings we guarantee |
62e76326 | 169 | * that a given nonce applies to only one user (barring attacks or |
170 | * really bad timing with expiry and creation). Using a random | |
171 | * component in the nonce allows us to loop to find a unique nonce. | |
172 | * We use H(nonce_data) so the nonce is meaningless to the reciever. | |
173 | * So our nonce looks like base64(H(timestamp,pointertohash,randomdata)) | |
174 | * And even if our randomness is not very random (probably due to | |
175 | * bad coding on my part) we don't really care - the timestamp and | |
82b045dc | 176 | * memory pointer also guarantee local uniqueness in the input to the hash |
177 | * function. | |
62e76326 | 178 | */ |
2d70df72 | 179 | |
180 | /* create a new nonce */ | |
181 | newnonce->nc = 0; | |
182 | newnonce->flags.valid = 1; | |
183 | newnonce->noncedata.self = newnonce; | |
184 | newnonce->noncedata.creationtime = current_time.tv_sec; | |
b600a19d | 185 | newnonce->noncedata.randomdata = squid_random(); |
2d70df72 | 186 | |
187 | authDigestNonceEncode(newnonce); | |
74830fc8 | 188 | /* |
189 | * loop until we get a unique nonce. The nonce creation must | |
190 | * have a random factor | |
191 | */ | |
62e76326 | 192 | |
2f44bd34 | 193 | while ((temp = authenticateDigestNonceFindNonce((char const *) (newnonce->key)))) { |
62e76326 | 194 | /* create a new nonce */ |
195 | newnonce->noncedata.randomdata = squid_random(); | |
196 | authDigestNonceEncode(newnonce); | |
2d70df72 | 197 | } |
62e76326 | 198 | |
4a8b20e8 | 199 | hash_join(digest_nonce_cache, newnonce); |
2d70df72 | 200 | /* the cache's link */ |
201 | authDigestNonceLink(newnonce); | |
202 | newnonce->flags.incache = 1; | |
4a7a3d56 | 203 | debugs(29, 5, "authenticateDigestNonceNew: created nonce " << newnonce << " at " << newnonce->noncedata.creationtime); |
2d70df72 | 204 | return newnonce; |
205 | } | |
206 | ||
c78aa667 | 207 | static void |
2d70df72 | 208 | authenticateDigestNonceDelete(digest_nonce_h * nonce) |
209 | { | |
210 | if (nonce) { | |
62e76326 | 211 | assert(nonce->references == 0); |
2d70df72 | 212 | #if UNREACHABLECODE |
62e76326 | 213 | |
214 | if (nonce->flags.incache) | |
215 | hash_remove_link(digest_nonce_cache, nonce); | |
216 | ||
2d70df72 | 217 | #endif |
62e76326 | 218 | |
219 | assert(nonce->flags.incache == 0); | |
220 | ||
221 | safe_free(nonce->key); | |
222 | ||
b001e822 | 223 | digest_nonce_pool->free(nonce); |
2d70df72 | 224 | } |
225 | } | |
226 | ||
c78aa667 | 227 | static void |
c193c972 | 228 | authenticateDigestNonceSetup(void) |
2d70df72 | 229 | { |
230 | if (!digest_nonce_pool) | |
04eb0689 | 231 | digest_nonce_pool = memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h)); |
62e76326 | 232 | |
2d70df72 | 233 | if (!digest_nonce_cache) { |
30abd221 | 234 | digest_nonce_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string); |
62e76326 | 235 | assert(digest_nonce_cache); |
f5691f9c | 236 | eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, digestConfig.nonceGCInterval, 1); |
2d70df72 | 237 | } |
238 | } | |
239 | ||
c78aa667 | 240 | static void |
c193c972 | 241 | authenticateDigestNonceShutdown(void) |
2d70df72 | 242 | { |
62e76326 | 243 | /* |
2d70df72 | 244 | * We empty the cache of any nonces left in there. |
245 | */ | |
246 | digest_nonce_h *nonce; | |
62e76326 | 247 | |
2d70df72 | 248 | if (digest_nonce_cache) { |
bf8fe701 | 249 | debugs(29, 2, "authenticateDigestNonceShutdown: Shutting down nonce cache "); |
62e76326 | 250 | hash_first(digest_nonce_cache); |
251 | ||
252 | while ((nonce = ((digest_nonce_h *) hash_next(digest_nonce_cache)))) { | |
253 | assert(nonce->flags.incache); | |
254 | authDigestNoncePurge(nonce); | |
255 | } | |
2d70df72 | 256 | } |
62e76326 | 257 | |
2f44bd34 | 258 | #if DEBUGSHUTDOWN |
2d70df72 | 259 | if (digest_nonce_pool) { |
b001e822 | 260 | delete digest_nonce_pool; |
261 | digest_nonce_pool = NULL; | |
2d70df72 | 262 | } |
62e76326 | 263 | |
2f44bd34 | 264 | #endif |
bf8fe701 | 265 | debugs(29, 2, "authenticateDigestNonceShutdown: Nonce cache shutdown"); |
2d70df72 | 266 | } |
267 | ||
c78aa667 | 268 | static void |
c193c972 | 269 | authenticateDigestNonceReconfigure(void) |
62e76326 | 270 | {} |
2d70df72 | 271 | |
c78aa667 | 272 | static void |
2d70df72 | 273 | authenticateDigestNonceCacheCleanup(void *data) |
274 | { | |
275 | /* | |
74830fc8 | 276 | * We walk the hash by nonceb64 as that is the unique key we |
277 | * use. For big hash tables we could consider stepping through | |
278 | * the cache, 100/200 entries at a time. Lets see how it flies | |
279 | * first. | |
2d70df72 | 280 | */ |
281 | digest_nonce_h *nonce; | |
bf8fe701 | 282 | debugs(29, 3, "authenticateDigestNonceCacheCleanup: Cleaning the nonce cache now"); |
4a7a3d56 | 283 | debugs(29, 3, "authenticateDigestNonceCacheCleanup: Current time: " << current_time.tv_sec); |
2d70df72 | 284 | hash_first(digest_nonce_cache); |
62e76326 | 285 | |
2d70df72 | 286 | while ((nonce = ((digest_nonce_h *) hash_next(digest_nonce_cache)))) { |
bf8fe701 | 287 | debugs(29, 3, "authenticateDigestNonceCacheCleanup: nonce entry : " << nonce << " '" << (char *) nonce->key << "'"); |
4a7a3d56 | 288 | debugs(29, 4, "authenticateDigestNonceCacheCleanup: Creation time: " << nonce->noncedata.creationtime); |
62e76326 | 289 | |
290 | if (authDigestNonceIsStale(nonce)) { | |
bf8fe701 | 291 | debugs(29, 4, "authenticateDigestNonceCacheCleanup: Removing nonce " << (char *) nonce->key << " from cache due to timeout."); |
62e76326 | 292 | assert(nonce->flags.incache); |
293 | /* invalidate nonce so future requests fail */ | |
294 | nonce->flags.valid = 0; | |
295 | /* if it is tied to a auth_user, remove the tie */ | |
296 | authDigestNonceUserUnlink(nonce); | |
297 | authDigestNoncePurge(nonce); | |
298 | } | |
2d70df72 | 299 | } |
62e76326 | 300 | |
bf8fe701 | 301 | debugs(29, 3, "authenticateDigestNonceCacheCleanup: Finished cleaning the nonce cache."); |
62e76326 | 302 | |
f5691f9c | 303 | if (digestConfig.active()) |
304 | eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, digestConfig.nonceGCInterval, 1); | |
2d70df72 | 305 | } |
306 | ||
c78aa667 | 307 | static void |
2d70df72 | 308 | authDigestNonceLink(digest_nonce_h * nonce) |
309 | { | |
310 | assert(nonce != NULL); | |
311 | nonce->references++; | |
bf8fe701 | 312 | debugs(29, 9, "authDigestNonceLink: nonce '" << nonce << "' now at '" << nonce->references << "'."); |
2d70df72 | 313 | } |
314 | ||
c78aa667 | 315 | #if NOT_USED |
316 | static int | |
2d70df72 | 317 | authDigestNonceLinks(digest_nonce_h * nonce) |
318 | { | |
319 | if (!nonce) | |
62e76326 | 320 | return -1; |
321 | ||
2d70df72 | 322 | return nonce->references; |
323 | } | |
62e76326 | 324 | |
c78aa667 | 325 | #endif |
2d70df72 | 326 | |
c78aa667 | 327 | static void |
2d70df72 | 328 | authDigestNonceUnlink(digest_nonce_h * nonce) |
329 | { | |
330 | assert(nonce != NULL); | |
62e76326 | 331 | |
2d70df72 | 332 | if (nonce->references > 0) { |
62e76326 | 333 | nonce->references--; |
2d70df72 | 334 | } else { |
bf8fe701 | 335 | debugs(29, 1, "authDigestNonceUnlink; Attempt to lower nonce " << nonce << " refcount below 0!"); |
2d70df72 | 336 | } |
62e76326 | 337 | |
bf8fe701 | 338 | debugs(29, 9, "authDigestNonceUnlink: nonce '" << nonce << "' now at '" << nonce->references << "'."); |
62e76326 | 339 | |
2d70df72 | 340 | if (nonce->references == 0) |
62e76326 | 341 | authenticateDigestNonceDelete(nonce); |
2d70df72 | 342 | } |
343 | ||
c78aa667 | 344 | static const char * |
2d70df72 | 345 | authenticateDigestNonceNonceb64(digest_nonce_h * nonce) |
346 | { | |
347 | if (!nonce) | |
62e76326 | 348 | return NULL; |
349 | ||
2f44bd34 | 350 | return (char const *) nonce->key; |
2d70df72 | 351 | } |
352 | ||
c78aa667 | 353 | static digest_nonce_h * |
2d70df72 | 354 | authenticateDigestNonceFindNonce(const char *nonceb64) |
355 | { | |
356 | digest_nonce_h *nonce = NULL; | |
62e76326 | 357 | |
2d70df72 | 358 | if (nonceb64 == NULL) |
62e76326 | 359 | return NULL; |
360 | ||
26ac0430 | 361 | debugs(29, 9, "authDigestNonceFindNonce:looking for nonceb64 '" << nonceb64 << "' in the nonce cache."); |
62e76326 | 362 | |
2f44bd34 | 363 | nonce = static_cast < digest_nonce_h * >(hash_lookup(digest_nonce_cache, nonceb64)); |
62e76326 | 364 | |
e6ccf245 | 365 | if ((nonce == NULL) || (strcmp(authenticateDigestNonceNonceb64(nonce), nonceb64))) |
62e76326 | 366 | return NULL; |
367 | ||
bf8fe701 | 368 | debugs(29, 9, "authDigestNonceFindNonce: Found nonce '" << nonce << "'"); |
62e76326 | 369 | |
2d70df72 | 370 | return nonce; |
371 | } | |
372 | ||
c78aa667 | 373 | static int |
2d70df72 | 374 | authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9]) |
375 | { | |
d205783b | 376 | unsigned long intnc; |
2d70df72 | 377 | /* do we have a nonce ? */ |
62e76326 | 378 | |
2d70df72 | 379 | if (!nonce) |
62e76326 | 380 | return 0; |
381 | ||
d205783b | 382 | intnc = strtol(nc, NULL, 16); |
62e76326 | 383 | |
f5292c64 | 384 | /* has it already been invalidated ? */ |
385 | if (!nonce->flags.valid) { | |
bf8fe701 | 386 | debugs(29, 4, "authDigestNonceIsValid: Nonce already invalidated"); |
f5292c64 | 387 | return 0; |
388 | } | |
389 | ||
390 | /* is the nonce-count ok ? */ | |
f5691f9c | 391 | if (!digestConfig.CheckNonceCount) { |
f5292c64 | 392 | nonce->nc++; |
393 | return -1; /* forced OK by configuration */ | |
394 | } | |
395 | ||
f5691f9c | 396 | if ((digestConfig.NonceStrictness && intnc != nonce->nc + 1) || |
62e76326 | 397 | intnc < nonce->nc + 1) { |
bf8fe701 | 398 | debugs(29, 4, "authDigestNonceIsValid: Nonce count doesn't match"); |
62e76326 | 399 | nonce->flags.valid = 0; |
400 | return 0; | |
2d70df72 | 401 | } |
62e76326 | 402 | |
2d70df72 | 403 | /* seems ok */ |
d205783b | 404 | /* increment the nonce count - we've already checked that intnc is a |
405 | * valid representation for us, so we don't need the test here. | |
406 | */ | |
407 | nonce->nc = intnc; | |
62e76326 | 408 | |
2d70df72 | 409 | return -1; |
410 | } | |
411 | ||
c78aa667 | 412 | static int |
2d70df72 | 413 | authDigestNonceIsStale(digest_nonce_h * nonce) |
414 | { | |
415 | /* do we have a nonce ? */ | |
62e76326 | 416 | |
2d70df72 | 417 | if (!nonce) |
62e76326 | 418 | return -1; |
419 | ||
2d70df72 | 420 | /* has it's max duration expired? */ |
f5691f9c | 421 | if (nonce->noncedata.creationtime + digestConfig.noncemaxduration < current_time.tv_sec) { |
bf8fe701 | 422 | debugs(29, 4, "authDigestNonceIsStale: Nonce is too old. " << |
4a7a3d56 | 423 | nonce->noncedata.creationtime << " " << |
424 | digestConfig.noncemaxduration << " " << | |
425 | current_time.tv_sec); | |
bf8fe701 | 426 | |
62e76326 | 427 | nonce->flags.valid = 0; |
428 | return -1; | |
2d70df72 | 429 | } |
62e76326 | 430 | |
2d70df72 | 431 | if (nonce->nc > 99999998) { |
bf8fe701 | 432 | debugs(29, 4, "authDigestNonceIsStale: Nonce count overflow"); |
62e76326 | 433 | nonce->flags.valid = 0; |
434 | return -1; | |
2d70df72 | 435 | } |
62e76326 | 436 | |
f5691f9c | 437 | if (nonce->nc > digestConfig.noncemaxuses) { |
bf8fe701 | 438 | debugs(29, 4, "authDigestNoncelastRequest: Nonce count over user limit"); |
62e76326 | 439 | nonce->flags.valid = 0; |
440 | return -1; | |
2d70df72 | 441 | } |
62e76326 | 442 | |
2d70df72 | 443 | /* seems ok */ |
444 | return 0; | |
445 | } | |
446 | ||
62e76326 | 447 | /* return -1 if the digest will be stale on the next request */ |
c78aa667 | 448 | static int |
2d70df72 | 449 | authDigestNonceLastRequest(digest_nonce_h * nonce) |
450 | { | |
451 | if (!nonce) | |
62e76326 | 452 | return -1; |
453 | ||
2d70df72 | 454 | if (nonce->nc == 99999997) { |
bf8fe701 | 455 | debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to overflow"); |
62e76326 | 456 | return -1; |
2d70df72 | 457 | } |
62e76326 | 458 | |
f5691f9c | 459 | if (nonce->nc >= digestConfig.noncemaxuses - 1) { |
bf8fe701 | 460 | debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to hit user limit"); |
62e76326 | 461 | return -1; |
2d70df72 | 462 | } |
62e76326 | 463 | |
2d70df72 | 464 | /* and other tests are possible. */ |
465 | return 0; | |
466 | } | |
467 | ||
c78aa667 | 468 | static void |
2d70df72 | 469 | authDigestNoncePurge(digest_nonce_h * nonce) |
470 | { | |
471 | if (!nonce) | |
62e76326 | 472 | return; |
473 | ||
2d70df72 | 474 | if (!nonce->flags.incache) |
62e76326 | 475 | return; |
476 | ||
4a8b20e8 | 477 | hash_remove_link(digest_nonce_cache, nonce); |
62e76326 | 478 | |
2d70df72 | 479 | nonce->flags.incache = 0; |
62e76326 | 480 | |
2d70df72 | 481 | /* the cache's link */ |
482 | authDigestNonceUnlink(nonce); | |
483 | } | |
484 | ||
62e76326 | 485 | /* USER related functions */ |
e1f7507e | 486 | static AuthUser * |
2d70df72 | 487 | authDigestUserFindUsername(const char *username) |
488 | { | |
e1f7507e AJ |
489 | AuthUserHashPointer *usernamehash; |
490 | AuthUser *auth_user; | |
491 | debugs(29, 9, HERE << "Looking for user '" << username << "'"); | |
62e76326 | 492 | |
2f44bd34 | 493 | if (username && (usernamehash = static_cast < auth_user_hash_pointer * >(hash_lookup(proxy_auth_username_cache, username)))) { |
f5691f9c | 494 | while ((usernamehash->user()->auth_type != AUTH_DIGEST) && |
62e76326 | 495 | (usernamehash->next)) |
496 | usernamehash = static_cast < auth_user_hash_pointer * >(usernamehash->next); | |
497 | ||
498 | auth_user = NULL; | |
499 | ||
f5691f9c | 500 | if (usernamehash->user()->auth_type == AUTH_DIGEST) { |
501 | auth_user = usernamehash->user(); | |
62e76326 | 502 | } |
503 | ||
504 | return auth_user; | |
2d70df72 | 505 | } |
62e76326 | 506 | |
2d70df72 | 507 | return NULL; |
508 | } | |
509 | ||
c78aa667 | 510 | static void |
c193c972 | 511 | authDigestUserShutdown(void) |
2d70df72 | 512 | { |
e1f7507e AJ |
513 | /** \todo Future work: the auth framework could flush it's cache */ |
514 | AuthUserHashPointer *usernamehash; | |
515 | AuthUser *auth_user; | |
2d70df72 | 516 | hash_first(proxy_auth_username_cache); |
62e76326 | 517 | |
2d70df72 | 518 | while ((usernamehash = ((auth_user_hash_pointer *) hash_next(proxy_auth_username_cache)))) { |
f5691f9c | 519 | auth_user = usernamehash->user(); |
62e76326 | 520 | |
f5691f9c | 521 | if (strcmp(auth_user->config->type(), "digest") == 0) |
522 | auth_user->unlock(); | |
2d70df72 | 523 | } |
2d70df72 | 524 | } |
525 | ||
e1f7507e | 526 | /** delete the digest request structure. Does NOT delete related structures */ |
f5691f9c | 527 | void |
528 | digestScheme::done() | |
2d70df72 | 529 | { |
e1f7507e | 530 | /** \todo this should be a Config call. */ |
2d70df72 | 531 | |
2d70df72 | 532 | if (digestauthenticators) |
62e76326 | 533 | helperShutdown(digestauthenticators); |
534 | ||
cb14509d HN |
535 | httpHeaderDestroyFieldsInfo(DigestFieldsInfo, DIGEST_ENUM_END); |
536 | DigestFieldsInfo = NULL; | |
537 | ||
2d70df72 | 538 | authdigest_initialised = 0; |
62e76326 | 539 | |
2d70df72 | 540 | if (!shutting_down) { |
62e76326 | 541 | authenticateDigestNonceReconfigure(); |
542 | return; | |
2d70df72 | 543 | } |
62e76326 | 544 | |
48d54e4d AJ |
545 | delete digestauthenticators; |
546 | digestauthenticators = NULL; | |
62e76326 | 547 | |
2d70df72 | 548 | authDigestUserShutdown(); |
549 | authenticateDigestNonceShutdown(); | |
bf8fe701 | 550 | debugs(29, 2, "authenticateDigestDone: Digest authentication shut down."); |
2d70df72 | 551 | } |
552 | ||
f5691f9c | 553 | void |
554 | AuthDigestConfig::dump(StoreEntry * entry, const char *name, AuthConfig * scheme) | |
2d70df72 | 555 | { |
f5691f9c | 556 | wordlist *list = authenticate; |
bf8fe701 | 557 | debugs(29, 9, "authDigestCfgDump: Dumping configuration"); |
2d70df72 | 558 | storeAppendPrintf(entry, "%s %s", name, "digest"); |
62e76326 | 559 | |
2d70df72 | 560 | while (list != NULL) { |
62e76326 | 561 | storeAppendPrintf(entry, " %s", list->key); |
562 | list = list->next; | |
2d70df72 | 563 | } |
62e76326 | 564 | |
404cfda1 | 565 | storeAppendPrintf(entry, "\n%s %s realm %s\n%s %s children %d startup=%d idle=%d concurrency=%d\n%s %s nonce_max_count %d\n%s %s nonce_max_duration %d seconds\n%s %s nonce_garbage_interval %d seconds\n", |
f5691f9c | 566 | name, "digest", digestAuthRealm, |
404cfda1 | 567 | name, "digest", authenticateChildren.n_max, authenticateChildren.n_startup, authenticateChildren.n_idle, authenticateChildren.concurrency, |
f5691f9c | 568 | name, "digest", noncemaxuses, |
569 | name, "digest", (int) noncemaxduration, | |
570 | name, "digest", (int) nonceGCInterval); | |
2d70df72 | 571 | } |
572 | ||
f5691f9c | 573 | bool |
574 | AuthDigestConfig::active() const | |
2d70df72 | 575 | { |
f5691f9c | 576 | return authdigest_initialised == 1; |
2d70df72 | 577 | } |
62e76326 | 578 | |
f5691f9c | 579 | bool |
580 | AuthDigestConfig::configured() const | |
2d70df72 | 581 | { |
f5691f9c | 582 | if ((authenticate != NULL) && |
48d54e4d | 583 | (authenticateChildren.n_max != 0) && |
f5691f9c | 584 | (digestAuthRealm != NULL) && (noncemaxduration > -1)) |
585 | return true; | |
62e76326 | 586 | |
f5691f9c | 587 | return false; |
2d70df72 | 588 | } |
589 | ||
82b045dc | 590 | int |
f5691f9c | 591 | AuthDigestUserRequest::authenticated() const |
2d70df72 | 592 | { |
82b045dc | 593 | if (credentials() == Ok) |
62e76326 | 594 | return 1; |
82b045dc | 595 | |
596 | return 0; | |
2d70df72 | 597 | } |
598 | ||
e1f7507e | 599 | /** log a digest user in |
2d70df72 | 600 | */ |
82b045dc | 601 | void |
486bf0fb | 602 | AuthDigestUserRequest::authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type) |
2d70df72 | 603 | { |
e1f7507e | 604 | AuthUser *auth_user; |
f5691f9c | 605 | AuthDigestUserRequest *digest_request; |
2d70df72 | 606 | digest_user_h *digest_user; |
607 | ||
608 | HASHHEX SESSIONKEY; | |
609 | HASHHEX HA2 = ""; | |
610 | HASHHEX Response; | |
611 | ||
82b045dc | 612 | assert(authUser() != NULL); |
613 | auth_user = authUser(); | |
2d70df72 | 614 | |
f5691f9c | 615 | digest_user = dynamic_cast < digest_user_h * >(auth_user); |
2d70df72 | 616 | |
7da7b01f | 617 | assert(digest_user != NULL); |
618 | ||
9bea1d5b | 619 | /* if the check has corrupted the user, just return */ |
62e76326 | 620 | |
82b045dc | 621 | if (credentials() == Failed) { |
62e76326 | 622 | return; |
bd507204 | 623 | } |
62e76326 | 624 | |
82b045dc | 625 | digest_request = this; |
2d70df72 | 626 | |
627 | /* do we have the HA1 */ | |
62e76326 | 628 | |
2d70df72 | 629 | if (!digest_user->HA1created) { |
82b045dc | 630 | credentials(Pending); |
62e76326 | 631 | return; |
2d70df72 | 632 | } |
62e76326 | 633 | |
2d70df72 | 634 | if (digest_request->nonce == NULL) { |
62e76326 | 635 | /* this isn't a nonce we issued */ |
82b045dc | 636 | credentials(Failed); |
62e76326 | 637 | return; |
2d70df72 | 638 | } |
62e76326 | 639 | |
2d70df72 | 640 | DigestCalcHA1(digest_request->algorithm, NULL, NULL, NULL, |
62e76326 | 641 | authenticateDigestNonceNonceb64(digest_request->nonce), |
642 | digest_request->cnonce, | |
643 | digest_user->HA1, SESSIONKEY); | |
2d70df72 | 644 | DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce), |
62e76326 | 645 | digest_request->nc, digest_request->cnonce, digest_request->qop, |
60745f24 | 646 | RequestMethodStr(request->method), digest_request->uri, HA2, Response); |
2d70df72 | 647 | |
bf8fe701 | 648 | debugs(29, 9, "\nResponse = '" << digest_request->response << "'\nsquid is = '" << Response << "'"); |
2d70df72 | 649 | |
df80d445 | 650 | if (strcasecmp(digest_request->response, Response) != 0) { |
651 | if (!digest_request->flags.helper_queried) { | |
652 | /* Query the helper in case the password has changed */ | |
653 | digest_request->flags.helper_queried = 1; | |
654 | digest_request->credentials_ok = Pending; | |
655 | return; | |
656 | } | |
657 | ||
f5691f9c | 658 | if (digestConfig.PostWorkaround && request->method != METHOD_GET) { |
f5292c64 | 659 | /* Ugly workaround for certain very broken browsers using the |
660 | * wrong method to calculate the request-digest on POST request. | |
661 | * This should be deleted once Digest authentication becomes more | |
662 | * widespread and such broken browsers no longer are commonly | |
663 | * used. | |
664 | */ | |
665 | DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce), | |
666 | digest_request->nc, digest_request->cnonce, digest_request->qop, | |
60745f24 | 667 | RequestMethodStr(METHOD_GET), digest_request->uri, HA2, Response); |
f5292c64 | 668 | |
669 | if (strcasecmp(digest_request->response, Response)) { | |
670 | credentials(Failed); | |
0a0c70cd | 671 | digest_request->setDenyMessage("Incorrect password"); |
f5292c64 | 672 | return; |
673 | } else { | |
a9925b40 | 674 | const char *useragent = request->header.getStr(HDR_USER_AGENT); |
f5292c64 | 675 | |
f7fbb7a5 | 676 | static IpAddress last_broken_addr; |
c3edaed6 | 677 | static int seen_broken_client = 0; |
678 | ||
679 | if (!seen_broken_client) { | |
cc192b50 | 680 | last_broken_addr.SetNoAddr(); |
c3edaed6 | 681 | seen_broken_client = 1; |
682 | } | |
f5292c64 | 683 | |
cc192b50 | 684 | if (last_broken_addr != request->client_addr) { |
bf8fe701 | 685 | debugs(29, 1, "\nDigest POST bug detected from " << |
cc192b50 | 686 | request->client_addr << " using '" << |
bf8fe701 | 687 | (useragent ? useragent : "-") << |
688 | "'. Please upgrade browser. See Bug #630 for details."); | |
689 | ||
f5292c64 | 690 | last_broken_addr = request->client_addr; |
691 | } | |
692 | } | |
693 | } else { | |
694 | credentials(Failed); | |
9f709824 | 695 | digest_request->flags.invalid_password = 1; |
0a0c70cd | 696 | digest_request->setDenyMessage("Incorrect password"); |
f5292c64 | 697 | return; |
698 | } | |
699 | ||
700 | /* check for stale nonce */ | |
701 | if (!authDigestNonceIsValid(digest_request->nonce, digest_request->nc)) { | |
bf8fe701 | 702 | debugs(29, 3, "authenticateDigestAuthenticateuser: user '" << digest_user->username() << "' validated OK but nonce stale"); |
f5292c64 | 703 | credentials(Failed); |
0a0c70cd | 704 | digest_request->setDenyMessage("Stale nonce"); |
f5292c64 | 705 | return; |
706 | } | |
2d70df72 | 707 | } |
62e76326 | 708 | |
82b045dc | 709 | credentials(Ok); |
f5292c64 | 710 | |
2d70df72 | 711 | /* password was checked and did match */ |
bf8fe701 | 712 | debugs(29, 4, "authenticateDigestAuthenticateuser: user '" << digest_user->username() << "' validated OK"); |
2d70df72 | 713 | |
714 | /* auth_user is now linked, we reset these values | |
715 | * after external auth occurs anyway */ | |
716 | auth_user->expiretime = current_time.tv_sec; | |
2d70df72 | 717 | return; |
718 | } | |
719 | ||
82b045dc | 720 | int |
f5691f9c | 721 | AuthDigestUserRequest::module_direction() |
2d70df72 | 722 | { |
82b045dc | 723 | switch (credentials()) { |
62e76326 | 724 | |
82b045dc | 725 | case Unchecked: |
62e76326 | 726 | return -1; |
727 | ||
82b045dc | 728 | case Ok: |
62e76326 | 729 | |
62e76326 | 730 | return 0; |
731 | ||
82b045dc | 732 | case Pending: |
62e76326 | 733 | return -1; |
734 | ||
82b045dc | 735 | case Failed: |
f5292c64 | 736 | |
26ac0430 AJ |
737 | /* send new challenge */ |
738 | return 1; | |
2d70df72 | 739 | } |
62e76326 | 740 | |
2d70df72 | 741 | return -2; |
742 | } | |
743 | ||
744 | /* add the [proxy]authorisation header */ | |
82b045dc | 745 | void |
f5691f9c | 746 | AuthDigestUserRequest::addHeader(HttpReply * rep, int accel) |
2d70df72 | 747 | { |
6e3d4bbc | 748 | http_hdr_type type; |
62e76326 | 749 | |
2d70df72 | 750 | /* don't add to authentication error pages */ |
82b045dc | 751 | |
2d70df72 | 752 | if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED) |
62e76326 | 753 | || (accel && rep->sline.status == HTTP_UNAUTHORIZED)) |
754 | return; | |
755 | ||
2d70df72 | 756 | type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO; |
757 | ||
758 | #if WAITING_FOR_TE | |
759 | /* test for http/1.1 transfer chunked encoding */ | |
760 | if (chunkedtest) | |
62e76326 | 761 | return; |
762 | ||
2d70df72 | 763 | #endif |
764 | ||
f5691f9c | 765 | if ((digestConfig.authenticate) && authDigestNonceLastRequest(nonce)) { |
82b045dc | 766 | flags.authinfo_sent = 1; |
bf8fe701 | 767 | debugs(29, 9, "authDigestAddHead: Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce) << "\""); |
82b045dc | 768 | httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce)); |
2d70df72 | 769 | } |
770 | } | |
771 | ||
772 | #if WAITING_FOR_TE | |
773 | /* add the [proxy]authorisation header */ | |
f5691f9c | 774 | void |
775 | AuthDigestUserRequest::addTrailer(HttpReply * rep, int accel) | |
2d70df72 | 776 | { |
777 | int type; | |
62e76326 | 778 | |
2d70df72 | 779 | if (!auth_user_request) |
62e76326 | 780 | return; |
781 | ||
62e76326 | 782 | |
2d70df72 | 783 | /* has the header already been send? */ |
f5691f9c | 784 | if (flags.authinfo_sent) |
62e76326 | 785 | return; |
786 | ||
2d70df72 | 787 | /* don't add to authentication error pages */ |
788 | if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED) | |
62e76326 | 789 | || (accel && rep->sline.status == HTTP_UNAUTHORIZED)) |
790 | return; | |
791 | ||
2d70df72 | 792 | type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO; |
793 | ||
f5691f9c | 794 | if ((digestConfig.authenticate) && authDigestNonceLastRequest(nonce)) { |
bf8fe701 | 795 | debugs(29, 9, "authDigestAddTrailer: Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce) << "\""); |
f5691f9c | 796 | httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce)); |
2d70df72 | 797 | } |
798 | } | |
62e76326 | 799 | |
2d70df72 | 800 | #endif |
801 | ||
802 | /* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */ | |
803 | void | |
18ec8500 | 804 | AuthDigestConfig::fixHeader(AuthUserRequest *auth_user_request, HttpReply *rep, http_hdr_type hdrType, HttpRequest * request) |
2d70df72 | 805 | { |
f5691f9c | 806 | if (!authenticate) |
82b045dc | 807 | return; |
808 | ||
a598b010 | 809 | int stale = 0; |
62e76326 | 810 | |
f5691f9c | 811 | if (auth_user_request) { |
812 | AuthDigestUserRequest *digest_request; | |
813 | digest_request = dynamic_cast < AuthDigestUserRequest * >(auth_user_request); | |
7da7b01f | 814 | assert (digest_request != NULL); |
62e76326 | 815 | |
9f709824 | 816 | stale = !digest_request->flags.invalid_password; |
2d70df72 | 817 | } |
82b045dc | 818 | |
819 | /* on a 407 or 401 we always use a new nonce */ | |
820 | digest_nonce_h *nonce = authenticateDigestNonceNew(); | |
821 | ||
18ec8500 | 822 | debugs(29, 9, "authenticateFixHeader: Sending type:" << hdrType << |
bf8fe701 | 823 | " header: 'Digest realm=\"" << digestAuthRealm << "\", nonce=\"" << |
824 | authenticateDigestNonceNonceb64(nonce) << "\", qop=\"" << QOP_AUTH << | |
825 | "\", stale=" << (stale ? "true" : "false")); | |
82b045dc | 826 | |
827 | /* in the future, for WWW auth we may want to support the domain entry */ | |
18ec8500 | 828 | httpHeaderPutStrf(&rep->header, hdrType, "Digest realm=\"%s\", nonce=\"%s\", qop=\"%s\", stale=%s", digestAuthRealm, authenticateDigestNonceNonceb64(nonce), QOP_AUTH, stale ? "true" : "false"); |
2d70df72 | 829 | } |
830 | ||
f5691f9c | 831 | DigestUser::~DigestUser() |
2d70df72 | 832 | { |
82b045dc | 833 | |
834 | dlink_node *link, *tmplink; | |
835 | link = nonces.head; | |
62e76326 | 836 | |
2d70df72 | 837 | while (link) { |
62e76326 | 838 | tmplink = link; |
839 | link = link->next; | |
82b045dc | 840 | dlinkDelete(tmplink, &nonces); |
62e76326 | 841 | authDigestNoncePurge(static_cast < digest_nonce_h * >(tmplink->data)); |
842 | authDigestNonceUnlink(static_cast < digest_nonce_h * >(tmplink->data)); | |
843 | dlinkNodeDelete(tmplink); | |
2d70df72 | 844 | } |
2d70df72 | 845 | } |
846 | ||
847 | static void | |
848 | authenticateDigestHandleReply(void *data, char *reply) | |
849 | { | |
82b045dc | 850 | DigestAuthenticateStateData *replyData = static_cast < DigestAuthenticateStateData * >(data); |
76f142cd | 851 | AuthUserRequest *auth_user_request; |
f5691f9c | 852 | AuthDigestUserRequest *digest_request; |
2d70df72 | 853 | digest_user_h *digest_user; |
2d70df72 | 854 | char *t = NULL; |
fa80a8ef | 855 | void *cbdata; |
bf8fe701 | 856 | debugs(29, 9, "authenticateDigestHandleReply: {" << (reply ? reply : "<NULL>") << "}"); |
62e76326 | 857 | |
2d70df72 | 858 | if (reply) { |
62e76326 | 859 | if ((t = strchr(reply, ' '))) |
0a0c70cd | 860 | *t++ = '\0'; |
62e76326 | 861 | |
778c696f | 862 | if (*reply == '\0' || *reply == '\n') |
62e76326 | 863 | reply = NULL; |
2d70df72 | 864 | } |
62e76326 | 865 | |
82b045dc | 866 | assert(replyData->auth_user_request != NULL); |
867 | auth_user_request = replyData->auth_user_request; | |
f5691f9c | 868 | digest_request = dynamic_cast < AuthDigestUserRequest * >(auth_user_request); |
869 | assert(digest_request); | |
7da7b01f | 870 | |
f5691f9c | 871 | digest_user = dynamic_cast < digest_user_h * >(auth_user_request->user()); |
7da7b01f | 872 | assert(digest_user != NULL); |
62e76326 | 873 | |
0a0c70cd | 874 | if (reply && (strncasecmp(reply, "ERR", 3) == 0)) { |
f5691f9c | 875 | digest_request->credentials(AuthDigestUserRequest::Failed); |
26ac0430 | 876 | digest_request->flags.invalid_password = 1; |
0a0c70cd | 877 | |
878 | if (t && *t) | |
879 | digest_request->setDenyMessage(t); | |
880 | } else if (reply) { | |
62e76326 | 881 | CvtBin(reply, digest_user->HA1); |
882 | digest_user->HA1created = 1; | |
2d70df72 | 883 | } |
62e76326 | 884 | |
82b045dc | 885 | if (cbdataReferenceValidDone(replyData->data, &cbdata)) |
886 | replyData->handler(cbdata, NULL); | |
62e76326 | 887 | |
8b45a856 | 888 | //we know replyData->auth_user_request != NULL, or we'd have asserted |
4f0ef8e8 | 889 | AUTHUSERREQUESTUNLOCK(replyData->auth_user_request, "replyData"); |
8b45a856 | 890 | |
82b045dc | 891 | cbdataFree(replyData); |
2d70df72 | 892 | } |
893 | ||
894 | /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the | |
895 | * config file */ | |
f5691f9c | 896 | void |
897 | AuthDigestConfig::init(AuthConfig * scheme) | |
2d70df72 | 898 | { |
f5691f9c | 899 | if (authenticate) { |
6d97f5f1 | 900 | DigestFieldsInfo = httpHeaderBuildFieldsInfo(DigestAttrs, DIGEST_ENUM_END); |
62e76326 | 901 | authenticateDigestNonceSetup(); |
902 | authdigest_initialised = 1; | |
903 | ||
904 | if (digestauthenticators == NULL) | |
48d54e4d | 905 | digestauthenticators = new helper("digestauthenticator"); |
62e76326 | 906 | |
f5691f9c | 907 | digestauthenticators->cmdline = authenticate; |
62e76326 | 908 | |
48d54e4d | 909 | digestauthenticators->childs = authenticateChildren; |
62e76326 | 910 | |
911 | digestauthenticators->ipc_type = IPC_STREAM; | |
912 | ||
913 | helperOpenServers(digestauthenticators); | |
914 | ||
62e76326 | 915 | CBDATA_INIT_TYPE(DigestAuthenticateStateData); |
2d70df72 | 916 | } |
917 | } | |
918 | ||
62ee09ca | 919 | void |
15fab853 | 920 | AuthDigestConfig::registerWithCacheManager(void) |
62ee09ca | 921 | { |
15fab853 | 922 | CacheManager::GetInstance()-> |
26ac0430 AJ |
923 | registerAction("digestauthenticator", |
924 | "Digest User Authenticator Stats", | |
925 | authenticateDigestStats, 0, 1); | |
62ee09ca | 926 | } |
2d70df72 | 927 | |
928 | /* free any allocated configuration details */ | |
929 | void | |
f5691f9c | 930 | AuthDigestConfig::done() |
2d70df72 | 931 | { |
f5691f9c | 932 | if (authenticate) |
933 | wordlistDestroy(&authenticate); | |
62e76326 | 934 | |
f5691f9c | 935 | safe_free(digestAuthRealm); |
936 | } | |
62e76326 | 937 | |
b23c9d89 | 938 | AuthDigestConfig::AuthDigestConfig() : authenticateChildren(20) |
f5691f9c | 939 | { |
940 | /* TODO: move into initialisation list */ | |
f5691f9c | 941 | /* 5 minutes */ |
942 | nonceGCInterval = 5 * 60; | |
943 | /* 30 minutes */ | |
944 | noncemaxduration = 30 * 60; | |
945 | /* 50 requests */ | |
946 | noncemaxuses = 50; | |
947 | /* Not strict nonce count behaviour */ | |
948 | NonceStrictness = 0; | |
949 | /* Verify nonce count */ | |
950 | CheckNonceCount = 1; | |
2d70df72 | 951 | } |
952 | ||
f5691f9c | 953 | void |
954 | AuthDigestConfig::parse(AuthConfig * scheme, int n_configured, char *param_str) | |
2d70df72 | 955 | { |
2d70df72 | 956 | if (strcasecmp(param_str, "program") == 0) { |
f5691f9c | 957 | if (authenticate) |
958 | wordlistDestroy(&authenticate); | |
62e76326 | 959 | |
f5691f9c | 960 | parse_wordlist(&authenticate); |
62e76326 | 961 | |
42900318 | 962 | requirePathnameExists("auth_param digest program", authenticate->key); |
2d70df72 | 963 | } else if (strcasecmp(param_str, "children") == 0) { |
48d54e4d | 964 | authenticateChildren.parseConfig(); |
2d70df72 | 965 | } else if (strcasecmp(param_str, "realm") == 0) { |
f5691f9c | 966 | parse_eol(&digestAuthRealm); |
2d70df72 | 967 | } else if (strcasecmp(param_str, "nonce_garbage_interval") == 0) { |
f5691f9c | 968 | parse_time_t(&nonceGCInterval); |
2d70df72 | 969 | } else if (strcasecmp(param_str, "nonce_max_duration") == 0) { |
f5691f9c | 970 | parse_time_t(&noncemaxduration); |
2d70df72 | 971 | } else if (strcasecmp(param_str, "nonce_max_count") == 0) { |
f5691f9c | 972 | parse_int((int *) &noncemaxuses); |
d205783b | 973 | } else if (strcasecmp(param_str, "nonce_strictness") == 0) { |
f5691f9c | 974 | parse_onoff(&NonceStrictness); |
f5292c64 | 975 | } else if (strcasecmp(param_str, "check_nonce_count") == 0) { |
f5691f9c | 976 | parse_onoff(&CheckNonceCount); |
f5292c64 | 977 | } else if (strcasecmp(param_str, "post_workaround") == 0) { |
f5691f9c | 978 | parse_onoff(&PostWorkaround); |
f741d2f6 HN |
979 | } else if (strcasecmp(param_str, "utf8") == 0) { |
980 | parse_onoff(&utf8); | |
2d70df72 | 981 | } else { |
bf8fe701 | 982 | debugs(29, 0, "unrecognised digest auth scheme parameter '" << param_str << "'"); |
2d70df72 | 983 | } |
984 | } | |
985 | ||
f5691f9c | 986 | const char * |
987 | AuthDigestConfig::type() const | |
988 | { | |
989 | return digestScheme::GetInstance().type(); | |
990 | } | |
991 | ||
2d70df72 | 992 | |
993 | static void | |
994 | authenticateDigestStats(StoreEntry * sentry) | |
995 | { | |
9522b380 | 996 | helperStats(sentry, digestauthenticators, "Digest Authenticator Statistics"); |
2d70df72 | 997 | } |
998 | ||
999 | /* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */ | |
1000 | ||
c78aa667 | 1001 | static void |
2d70df72 | 1002 | authDigestNonceUserUnlink(digest_nonce_h * nonce) |
1003 | { | |
1004 | digest_user_h *digest_user; | |
1005 | dlink_node *link, *tmplink; | |
62e76326 | 1006 | |
2d70df72 | 1007 | if (!nonce) |
62e76326 | 1008 | return; |
1009 | ||
f5691f9c | 1010 | if (!nonce->user) |
62e76326 | 1011 | return; |
1012 | ||
f5691f9c | 1013 | digest_user = nonce->user; |
62e76326 | 1014 | |
1015 | /* unlink from the user list. Yes we're crossing structures but this is the only | |
2d70df72 | 1016 | * time this code is needed |
1017 | */ | |
1018 | link = digest_user->nonces.head; | |
62e76326 | 1019 | |
2d70df72 | 1020 | while (link) { |
62e76326 | 1021 | tmplink = link; |
1022 | link = link->next; | |
1023 | ||
1024 | if (tmplink->data == nonce) { | |
1025 | dlinkDelete(tmplink, &digest_user->nonces); | |
1026 | authDigestNonceUnlink(static_cast < digest_nonce_h * >(tmplink->data)); | |
1027 | dlinkNodeDelete(tmplink); | |
1028 | link = NULL; | |
1029 | } | |
2d70df72 | 1030 | } |
62e76326 | 1031 | |
f5691f9c | 1032 | /* this reference to user was not locked because freeeing the user frees |
26ac0430 | 1033 | * the nonce too. |
2d70df72 | 1034 | */ |
f5691f9c | 1035 | nonce->user = NULL; |
2d70df72 | 1036 | } |
1037 | ||
1038 | /* authDigestUserLinkNonce: add a nonce to a given user's struct */ | |
1039 | ||
c78aa667 | 1040 | static void |
f5691f9c | 1041 | authDigestUserLinkNonce(DigestUser * user, digest_nonce_h * nonce) |
2d70df72 | 1042 | { |
1043 | dlink_node *node; | |
1044 | digest_user_h *digest_user; | |
62e76326 | 1045 | |
f5691f9c | 1046 | if (!user || !nonce) |
62e76326 | 1047 | return; |
1048 | ||
f5691f9c | 1049 | digest_user = user; |
62e76326 | 1050 | |
2d70df72 | 1051 | node = digest_user->nonces.head; |
62e76326 | 1052 | |
2d70df72 | 1053 | while (node && (node->data != nonce)) |
62e76326 | 1054 | node = node->next; |
1055 | ||
2d70df72 | 1056 | if (node) |
62e76326 | 1057 | return; |
1058 | ||
2d70df72 | 1059 | node = dlinkNodeNew(); |
62e76326 | 1060 | |
2d70df72 | 1061 | dlinkAddTail(nonce, node, &digest_user->nonces); |
62e76326 | 1062 | |
2d70df72 | 1063 | authDigestNonceLink(nonce); |
62e76326 | 1064 | |
2d70df72 | 1065 | /* ping this nonce to this auth user */ |
4e9d4067 | 1066 | assert((nonce->user == NULL) || (nonce->user == user)); |
62e76326 | 1067 | |
f5691f9c | 1068 | /* we don't lock this reference because removing the user removes the |
2d70df72 | 1069 | * hash too. Of course if that changes we're stuffed so read the code huh? |
1070 | */ | |
f5691f9c | 1071 | nonce->user = user; |
2d70df72 | 1072 | } |
1073 | ||
1074 | /* setup the necessary info to log the username */ | |
f5691f9c | 1075 | static AuthUserRequest * |
9f709824 | 1076 | authDigestLogUsername(char *username, AuthDigestUserRequest *auth_user_request) |
2d70df72 | 1077 | { |
f5691f9c | 1078 | assert(auth_user_request != NULL); |
2d70df72 | 1079 | |
1080 | /* log the username */ | |
bf8fe701 | 1081 | debugs(29, 9, "authDigestLogUsername: Creating new user for logging '" << username << "'"); |
f5691f9c | 1082 | digest_user_h *digest_user = new DigestUser(&digestConfig); |
2d70df72 | 1083 | /* save the credentials */ |
f5691f9c | 1084 | digest_user->username(username); |
2d70df72 | 1085 | /* set the auth_user type */ |
f5691f9c | 1086 | digest_user->auth_type = AUTH_BROKEN; |
2d70df72 | 1087 | /* link the request to the user */ |
f5691f9c | 1088 | auth_user_request->authUser(digest_user); |
1089 | auth_user_request->user(digest_user); | |
1090 | digest_user->addRequest (auth_user_request); | |
1091 | return auth_user_request; | |
2d70df72 | 1092 | } |
1093 | ||
1094 | /* | |
1095 | * Decode a Digest [Proxy-]Auth string, placing the results in the passed | |
1096 | * Auth_user structure. | |
1097 | */ | |
f5691f9c | 1098 | AuthUserRequest * |
1099 | AuthDigestConfig::decode(char const *proxy_auth) | |
2d70df72 | 1100 | { |
2d70df72 | 1101 | const char *item; |
1102 | const char *p; | |
1103 | const char *pos = NULL; | |
1104 | char *username = NULL; | |
1105 | digest_nonce_h *nonce; | |
63c5fa4a | 1106 | int ilen; |
2d70df72 | 1107 | |
bf8fe701 | 1108 | debugs(29, 9, "authenticateDigestDecodeAuth: beginning"); |
2d70df72 | 1109 | |
f5691f9c | 1110 | AuthDigestUserRequest *digest_request = new AuthDigestUserRequest(); |
2d70df72 | 1111 | |
1112 | /* trim DIGEST from string */ | |
62e76326 | 1113 | |
ba53f4b8 | 1114 | while (xisgraph(*proxy_auth)) |
62e76326 | 1115 | proxy_auth++; |
2d70df72 | 1116 | |
1117 | /* Trim leading whitespace before decoding */ | |
1118 | while (xisspace(*proxy_auth)) | |
62e76326 | 1119 | proxy_auth++; |
2d70df72 | 1120 | |
30abd221 | 1121 | String temp(proxy_auth); |
62e76326 | 1122 | |
2d70df72 | 1123 | while (strListGetItem(&temp, ',', &item, &ilen, &pos)) { |
6d97f5f1 A |
1124 | String value; |
1125 | size_t nlen; | |
1126 | /* isolate directive name */ | |
1127 | if ((p = (const char *)memchr(item, '=', ilen)) && (p - item < ilen)) { | |
9abd1514 | 1128 | nlen = p++ - item; |
6d97f5f1 A |
1129 | if (!httpHeaderParseQuotedString(p, &value)) |
1130 | value.limitInit(p, ilen - (p - item)); | |
1131 | } else | |
1132 | nlen = ilen; | |
1133 | ||
1134 | if (!value.defined()) { | |
1135 | debugs(29, 9, "authDigestDecodeAuth: Failed to parse attribute '" << temp << "' in '" << proxy_auth << "'"); | |
1136 | continue; | |
1137 | } | |
9abd1514 | 1138 | |
6d97f5f1 A |
1139 | /* find type */ |
1140 | http_digest_attr_type type = (http_digest_attr_type)httpHeaderIdByName(item, nlen, DigestFieldsInfo, DIGEST_ENUM_END); | |
9abd1514 | 1141 | |
6d97f5f1 A |
1142 | switch (type) { |
1143 | case DIGEST_USERNAME: | |
bbe0ed86 | 1144 | safe_free(username); |
9abd1514 | 1145 | username = xstrndup(value.rawBuf(), value.size() + 1); |
bf8fe701 | 1146 | debugs(29, 9, "authDigestDecodeAuth: Found Username '" << username << "'"); |
6d97f5f1 | 1147 | break; |
62e76326 | 1148 | |
6d97f5f1 | 1149 | case DIGEST_REALM: |
bbe0ed86 | 1150 | safe_free(digest_request->realm); |
9abd1514 | 1151 | digest_request->realm = xstrndup(value.rawBuf(), value.size() + 1); |
bf8fe701 | 1152 | debugs(29, 9, "authDigestDecodeAuth: Found realm '" << digest_request->realm << "'"); |
6d97f5f1 | 1153 | break; |
62e76326 | 1154 | |
6d97f5f1 | 1155 | case DIGEST_QOP: |
bbe0ed86 | 1156 | safe_free(digest_request->qop); |
9abd1514 | 1157 | digest_request->qop = xstrndup(value.rawBuf(), value.size() + 1); |
bf8fe701 | 1158 | debugs(29, 9, "authDigestDecodeAuth: Found qop '" << digest_request->qop << "'"); |
6d97f5f1 | 1159 | break; |
62e76326 | 1160 | |
6d97f5f1 | 1161 | case DIGEST_ALGORITHM: |
bbe0ed86 | 1162 | safe_free(digest_request->algorithm); |
9abd1514 | 1163 | digest_request->algorithm = xstrndup(value.rawBuf(), value.size() + 1); |
bf8fe701 | 1164 | debugs(29, 9, "authDigestDecodeAuth: Found algorithm '" << digest_request->algorithm << "'"); |
6d97f5f1 | 1165 | break; |
62e76326 | 1166 | |
6d97f5f1 | 1167 | case DIGEST_URI: |
bbe0ed86 | 1168 | safe_free(digest_request->uri); |
9abd1514 | 1169 | digest_request->uri = xstrndup(value.rawBuf(), value.size() + 1); |
bf8fe701 | 1170 | debugs(29, 9, "authDigestDecodeAuth: Found uri '" << digest_request->uri << "'"); |
6d97f5f1 | 1171 | break; |
62e76326 | 1172 | |
6d97f5f1 | 1173 | case DIGEST_NONCE: |
bbe0ed86 | 1174 | safe_free(digest_request->nonceb64); |
9abd1514 | 1175 | digest_request->nonceb64 = xstrndup(value.rawBuf(), value.size() + 1); |
bf8fe701 | 1176 | debugs(29, 9, "authDigestDecodeAuth: Found nonce '" << digest_request->nonceb64 << "'"); |
6d97f5f1 | 1177 | break; |
62e76326 | 1178 | |
6d97f5f1 A |
1179 | case DIGEST_NC: |
1180 | if (value.size() != 8) { | |
1181 | debugs(29, 9, "authDigestDecodeAuth: Invalid nc '" << value << "' in '" << temp << "'"); | |
1182 | } | |
9abd1514 | 1183 | xstrncpy(digest_request->nc, value.rawBuf(), value.size() + 1); |
bf8fe701 | 1184 | debugs(29, 9, "authDigestDecodeAuth: Found noncecount '" << digest_request->nc << "'"); |
6d97f5f1 | 1185 | break; |
62e76326 | 1186 | |
6d97f5f1 | 1187 | case DIGEST_CNONCE: |
bbe0ed86 | 1188 | safe_free(digest_request->cnonce); |
9abd1514 | 1189 | digest_request->cnonce = xstrndup(value.rawBuf(), value.size() + 1); |
bf8fe701 | 1190 | debugs(29, 9, "authDigestDecodeAuth: Found cnonce '" << digest_request->cnonce << "'"); |
6d97f5f1 | 1191 | break; |
62e76326 | 1192 | |
6d97f5f1 | 1193 | case DIGEST_RESPONSE: |
bbe0ed86 | 1194 | safe_free(digest_request->response); |
9abd1514 | 1195 | digest_request->response = xstrndup(value.rawBuf(), value.size() + 1); |
bf8fe701 | 1196 | debugs(29, 9, "authDigestDecodeAuth: Found response '" << digest_request->response << "'"); |
6d97f5f1 | 1197 | break; |
9abd1514 | 1198 | |
6d97f5f1 | 1199 | default: |
59a98343 | 1200 | debugs(29, 3, "authDigestDecodeAuth: Unknown attribute '" << item << "' in '" << temp << "'"); |
9abd1514 | 1201 | |
62e76326 | 1202 | } |
2d70df72 | 1203 | } |
62e76326 | 1204 | |
30abd221 | 1205 | temp.clean(); |
2d70df72 | 1206 | |
1207 | ||
1208 | /* now we validate the data given to us */ | |
1209 | ||
74830fc8 | 1210 | /* |
1211 | * TODO: on invalid parameters we should return 400, not 407. | |
1212 | * Find some clean way of doing this. perhaps return a valid | |
1213 | * struct, and set the direction to clientwards combined with | |
1214 | * a change to the clientwards handling code (ie let the | |
1215 | * clientwards call set the error type (but limited to known | |
1216 | * correct values - 400/401/407 | |
1217 | */ | |
2d70df72 | 1218 | |
59a98343 | 1219 | /* 2069 requirements */ |
62e76326 | 1220 | |
59a98343 HN |
1221 | /* do we have a username ? */ |
1222 | if (!username || username[0] == '\0') { | |
1223 | debugs(29, 2, "authenticateDigestDecode: Empty or not present username"); | |
9f709824 | 1224 | return authDigestLogUsername(username, digest_request); |
2d70df72 | 1225 | } |
62e76326 | 1226 | |
59a98343 HN |
1227 | /* do we have a realm ? */ |
1228 | if (!digest_request->realm || digest_request->realm[0] == '\0') { | |
1229 | debugs(29, 2, "authenticateDigestDecode: Empty or not present realm"); | |
9f709824 | 1230 | return authDigestLogUsername(username, digest_request); |
2d70df72 | 1231 | } |
62e76326 | 1232 | |
59a98343 HN |
1233 | /* and a nonce? */ |
1234 | if (!digest_request->nonceb64 || digest_request->nonceb64[0] == '\0') { | |
1235 | debugs(29, 2, "authenticateDigestDecode: Empty or not present nonce"); | |
9f709824 | 1236 | return authDigestLogUsername(username, digest_request); |
2d70df72 | 1237 | } |
62e76326 | 1238 | |
74830fc8 | 1239 | /* we can't check the URI just yet. We'll check it in the |
6649f955 | 1240 | * authenticate phase, but needs to be given */ |
59a98343 HN |
1241 | if (!digest_request->uri || digest_request->uri[0] == '\0') { |
1242 | debugs(29, 2, "authenticateDigestDecode: Missing URI field"); | |
6649f955 HN |
1243 | return authDigestLogUsername(username, digest_request); |
1244 | } | |
2d70df72 | 1245 | |
1246 | /* is the response the correct length? */ | |
2d70df72 | 1247 | if (!digest_request->response || strlen(digest_request->response) != 32) { |
59a98343 | 1248 | debugs(29, 2, "authenticateDigestDecode: Response length invalid"); |
9f709824 | 1249 | return authDigestLogUsername(username, digest_request); |
2d70df72 | 1250 | } |
62e76326 | 1251 | |
59a98343 HN |
1252 | /* check the algorithm is present and supported */ |
1253 | if (!digest_request->algorithm) | |
1254 | digest_request->algorithm = xstrndup("MD5", 4); | |
1255 | else if (strcmp(digest_request->algorithm, "MD5") | |
1256 | && strcmp(digest_request->algorithm, "MD5-sess")) { | |
d6b7a3c4 | 1257 | debugs(29, 2, "authenticateDigestDecode: invalid algorithm specified!"); |
9f709824 | 1258 | return authDigestLogUsername(username, digest_request); |
2d70df72 | 1259 | } |
62e76326 | 1260 | |
59a98343 HN |
1261 | /* 2617 requirements, indicated by qop */ |
1262 | if (digest_request->qop) { | |
1263 | ||
6d97f5f1 A |
1264 | /* check the qop is what we expected. */ |
1265 | if (strcmp(digest_request->qop, QOP_AUTH) != 0) { | |
1266 | /* we received a qop option we didn't send */ | |
1267 | debugs(29, 2, "authenticateDigestDecode: Invalid qop option received"); | |
1268 | return authDigestLogUsername(username, digest_request); | |
1269 | } | |
1270 | ||
1271 | /* check cnonce */ | |
1272 | if (!digest_request->cnonce || digest_request->cnonce[0] == '\0') { | |
1273 | debugs(29, 2, "authenticateDigestDecode: Missing cnonce field"); | |
1274 | return authDigestLogUsername(username, digest_request); | |
1275 | } | |
1276 | ||
1277 | /* check nc */ | |
1278 | if (strlen(digest_request->nc) != 8 || strspn(digest_request->nc, "0123456789abcdefABCDEF") != 8) { | |
1279 | debugs(29, 2, "authenticateDigestDecode: invalid nonce count"); | |
1280 | return authDigestLogUsername(username, digest_request); | |
1281 | } | |
59a98343 | 1282 | } else { |
6d97f5f1 A |
1283 | /* cnonce and nc both require qop */ |
1284 | if (digest_request->cnonce || digest_request->nc) { | |
1285 | debugs(29, 2, "authenticateDigestDecode: missing qop!"); | |
1286 | return authDigestLogUsername(username, digest_request); | |
1287 | } | |
2d70df72 | 1288 | } |
62e76326 | 1289 | |
59a98343 HN |
1290 | /** below nonce state dependent **/ |
1291 | ||
1292 | /* now the nonce */ | |
1293 | nonce = authenticateDigestNonceFindNonce(digest_request->nonceb64); | |
1294 | if (!nonce) { | |
1295 | /* we couldn't find a matching nonce! */ | |
1296 | debugs(29, 2, "authenticateDigestDecode: Unexpected or invalid nonce received"); | |
fa004391 | 1297 | digest_request->credentials(AuthDigestUserRequest::Failed); |
9f709824 | 1298 | return authDigestLogUsername(username, digest_request); |
2d70df72 | 1299 | } |
62e76326 | 1300 | |
59a98343 HN |
1301 | digest_request->nonce = nonce; |
1302 | authDigestNonceLink(nonce); | |
1303 | ||
1304 | /* check that we're not being hacked / the username hasn't changed */ | |
1305 | if (nonce->user && strcmp(username, nonce->user->username())) { | |
1306 | debugs(29, 2, "authenticateDigestDecode: Username for the nonce does not equal the username for the request"); | |
9f709824 | 1307 | return authDigestLogUsername(username, digest_request); |
2d70df72 | 1308 | } |
62e76326 | 1309 | |
2d70df72 | 1310 | /* the method we'll check at the authenticate step as well */ |
1311 | ||
1312 | ||
1313 | /* we don't send or parse opaques. Ok so we're flexable ... */ | |
1314 | ||
1315 | /* find the user */ | |
f5691f9c | 1316 | digest_user_h *digest_user; |
1317 | ||
e1f7507e | 1318 | AuthUser *auth_user; |
2d70df72 | 1319 | |
1320 | if ((auth_user = authDigestUserFindUsername(username)) == NULL) { | |
62e76326 | 1321 | /* the user doesn't exist in the username cache yet */ |
bf8fe701 | 1322 | debugs(29, 9, "authDigestDecodeAuth: Creating new digest user '" << username << "'"); |
f5691f9c | 1323 | digest_user = new DigestUser (&digestConfig); |
1324 | /* auth_user is a parent */ | |
1325 | auth_user = digest_user; | |
62e76326 | 1326 | /* save the username */ |
f5691f9c | 1327 | digest_user->username(username); |
62e76326 | 1328 | /* set the user type */ |
f5691f9c | 1329 | digest_user->auth_type = AUTH_DIGEST; |
62e76326 | 1330 | /* this auth_user struct is the one to get added to the |
1331 | * username cache */ | |
1332 | /* store user in hash's */ | |
f5691f9c | 1333 | digest_user->addToNameCache(); |
df80d445 | 1334 | |
62e76326 | 1335 | /* |
1336 | * Add the digest to the user so we can tell if a hacking | |
1337 | * or spoofing attack is taking place. We do this by assuming | |
1338 | * the user agent won't change user name without warning. | |
1339 | */ | |
f5691f9c | 1340 | authDigestUserLinkNonce(digest_user, nonce); |
2d70df72 | 1341 | } else { |
bf8fe701 | 1342 | debugs(29, 9, "authDigestDecodeAuth: Found user '" << username << "' in the user cache as '" << auth_user << "'"); |
f5691f9c | 1343 | digest_user = static_cast < digest_user_h * >(auth_user); |
62e76326 | 1344 | xfree(username); |
2d70df72 | 1345 | } |
62e76326 | 1346 | |
2d70df72 | 1347 | /*link the request and the user */ |
f5691f9c | 1348 | assert(digest_request != NULL); |
82b045dc | 1349 | |
f5691f9c | 1350 | digest_request->authUser (digest_user); |
62e76326 | 1351 | |
f5691f9c | 1352 | digest_request->user(digest_user); |
62e76326 | 1353 | |
f5691f9c | 1354 | digest_user->addRequest (digest_request); |
2d70df72 | 1355 | |
bf8fe701 | 1356 | debugs(29, 9, "username = '" << digest_user->username() << "'\nrealm = '" << |
1357 | digest_request->realm << "'\nqop = '" << digest_request->qop << | |
1358 | "'\nalgorithm = '" << digest_request->algorithm << "'\nuri = '" << | |
1359 | digest_request->uri << "'\nnonce = '" << digest_request->nonceb64 << | |
1360 | "'\nnc = '" << digest_request->nc << "'\ncnonce = '" << | |
1361 | digest_request->cnonce << "'\nresponse = '" << | |
1362 | digest_request->response << "'\ndigestnonce = '" << nonce << "'"); | |
2d70df72 | 1363 | |
f5691f9c | 1364 | return digest_request; |
2d70df72 | 1365 | } |
1366 | ||
1367 | /* send the initial data to a digest authenticator module */ | |
f5691f9c | 1368 | void |
1369 | AuthDigestUserRequest::module_start(RH * handler, void *data) | |
2d70df72 | 1370 | { |
e6ccf245 | 1371 | DigestAuthenticateStateData *r = NULL; |
2d70df72 | 1372 | char buf[8192]; |
2d70df72 | 1373 | digest_user_h *digest_user; |
f5691f9c | 1374 | assert(user()->auth_type == AUTH_DIGEST); |
1375 | digest_user = dynamic_cast < digest_user_h * >(user()); | |
7da7b01f | 1376 | assert(digest_user != NULL); |
bf8fe701 | 1377 | debugs(29, 9, "authenticateStart: '\"" << digest_user->username() << "\":\"" << realm << "\"'"); |
62e76326 | 1378 | |
f5691f9c | 1379 | if (digestConfig.authenticate == NULL) { |
62e76326 | 1380 | handler(data, NULL); |
1381 | return; | |
2d70df72 | 1382 | } |
62e76326 | 1383 | |
e6ccf245 | 1384 | r = cbdataAlloc(DigestAuthenticateStateData); |
2d70df72 | 1385 | r->handler = handler; |
fa80a8ef | 1386 | r->data = cbdataReference(data); |
f5691f9c | 1387 | r->auth_user_request = this; |
4f0ef8e8 | 1388 | AUTHUSERREQUESTLOCK(r->auth_user_request, "r"); |
f741d2f6 | 1389 | if (digestConfig.utf8) { |
076df709 FC |
1390 | char userstr[1024]; |
1391 | latin1_to_utf8(userstr, sizeof(userstr), digest_user->username()); | |
1392 | snprintf(buf, 8192, "\"%s\":\"%s\"\n", userstr, realm); | |
f741d2f6 | 1393 | } else { |
26ac0430 | 1394 | snprintf(buf, 8192, "\"%s\":\"%s\"\n", digest_user->username(), realm); |
f741d2f6 | 1395 | } |
8b45a856 | 1396 | |
2d70df72 | 1397 | helperSubmit(digestauthenticators, buf, authenticateDigestHandleReply, r); |
1398 | } | |
82b045dc | 1399 | |
076df709 | 1400 | DigestUser::DigestUser (AuthConfig *aConfig) : AuthUser (aConfig), HA1created (0) |
82b045dc | 1401 | {} |
1402 | ||
e1f7507e | 1403 | AuthUser * |
f5691f9c | 1404 | AuthDigestUserRequest::authUser() const |
82b045dc | 1405 | { |
f5691f9c | 1406 | return const_cast<AuthUser *>(user()); |
82b045dc | 1407 | } |
1408 | ||
1409 | void | |
e1f7507e | 1410 | AuthDigestUserRequest::authUser(AuthUser *aUser) |
82b045dc | 1411 | { |
e1f7507e | 1412 | assert(!authUser()); |
f5691f9c | 1413 | user(aUser); |
e1f7507e | 1414 | user()->lock(); |
82b045dc | 1415 | } |
1416 | ||
f5691f9c | 1417 | AuthDigestUserRequest::CredentialsState |
1418 | AuthDigestUserRequest::credentials() const | |
1419 | { | |
1420 | return credentials_ok; | |
f5691f9c | 1421 | } |
82b045dc | 1422 | |
f5691f9c | 1423 | void |
1424 | AuthDigestUserRequest::credentials(CredentialsState newCreds) | |
82b045dc | 1425 | { |
f5691f9c | 1426 | credentials_ok = newCreds; |
82b045dc | 1427 | } |
1428 | ||
f5691f9c | 1429 | AuthDigestUserRequest::AuthDigestUserRequest() : nonceb64(NULL) ,cnonce(NULL) ,realm(NULL), |
1430 | pszPass(NULL) ,algorithm(NULL) ,pszMethod(NULL), | |
1431 | qop(NULL) ,uri(NULL) ,response(NULL), | |
1432 | nonce(NULL), _theUser (NULL) , | |
1433 | credentials_ok (Unchecked) | |
1434 | {} | |
1435 | ||
e1f7507e | 1436 | /** delete the digest request structure. Does NOT delete related structures */ |
f5691f9c | 1437 | AuthDigestUserRequest::~AuthDigestUserRequest() |
82b045dc | 1438 | { |
f5691f9c | 1439 | safe_free (nonceb64); |
1440 | safe_free (cnonce); | |
1441 | safe_free (realm); | |
1442 | safe_free (pszPass); | |
1443 | safe_free (algorithm); | |
1444 | safe_free (pszMethod); | |
1445 | safe_free (qop); | |
1446 | safe_free (uri); | |
1447 | safe_free (response); | |
1448 | ||
1449 | if (nonce) | |
1450 | authDigestNonceUnlink(nonce); | |
82b045dc | 1451 | } |
1452 | ||
f5691f9c | 1453 | AuthConfig * |
1454 | digestScheme::createConfig() | |
82b045dc | 1455 | { |
f5691f9c | 1456 | return &digestConfig; |
82b045dc | 1457 | } |
f5691f9c | 1458 |