]> git.ipfire.org Git - thirdparty/squid.git/blob - src/auth/digest/auth_digest.cc
Merged from parent (trunk r11623, v3.2.0.10+).
[thirdparty/squid.git] / src / auth / digest / auth_digest.cc
1 /*
2 * $Id$
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.
23 *
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.
28 *
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/auth_digest.h"
43 #include "auth/digest/Scheme.h"
44 #include "auth/digest/User.h"
45 #include "auth/digest/UserRequest.h"
46 #include "auth/Gadgets.h"
47 #include "auth/State.h"
48 #include "base64.h"
49 #include "event.h"
50 #include "mgr/Registration.h"
51 #include "Store.h"
52 #include "HttpRequest.h"
53 #include "HttpReply.h"
54 #include "wordlist.h"
55 #include "SquidTime.h"
56
57 /* Digest Scheme */
58
59 static AUTHSSTATS authenticateDigestStats;
60
61 helper *digestauthenticators = NULL;
62
63 static hash_table *digest_nonce_cache;
64
65 static int authdigest_initialised = 0;
66 static MemAllocator *digest_nonce_pool = NULL;
67
68 enum http_digest_attr_type {
69 DIGEST_USERNAME,
70 DIGEST_REALM,
71 DIGEST_QOP,
72 DIGEST_ALGORITHM,
73 DIGEST_URI,
74 DIGEST_NONCE,
75 DIGEST_NC,
76 DIGEST_CNONCE,
77 DIGEST_RESPONSE,
78 DIGEST_ENUM_END
79 };
80
81 static const HttpHeaderFieldAttrs DigestAttrs[DIGEST_ENUM_END] = {
82 {"username", (http_hdr_type)DIGEST_USERNAME},
83 {"realm", (http_hdr_type)DIGEST_REALM},
84 {"qop", (http_hdr_type)DIGEST_QOP},
85 {"algorithm", (http_hdr_type)DIGEST_ALGORITHM},
86 {"uri", (http_hdr_type)DIGEST_URI},
87 {"nonce", (http_hdr_type)DIGEST_NONCE},
88 {"nc", (http_hdr_type)DIGEST_NC},
89 {"cnonce", (http_hdr_type)DIGEST_CNONCE},
90 {"response", (http_hdr_type)DIGEST_RESPONSE},
91 };
92
93 static HttpHeaderFieldInfo *DigestFieldsInfo = NULL;
94
95 /*
96 *
97 * Nonce Functions
98 *
99 */
100
101 static void authenticateDigestNonceCacheCleanup(void *data);
102 static digest_nonce_h *authenticateDigestNonceFindNonce(const char *nonceb64);
103 static digest_nonce_h *authenticateDigestNonceNew(void);
104 static void authenticateDigestNonceDelete(digest_nonce_h * nonce);
105 static void authenticateDigestNonceSetup(void);
106 static int authDigestNonceIsStale(digest_nonce_h * nonce);
107 static void authDigestNonceEncode(digest_nonce_h * nonce);
108 static void authDigestNonceLink(digest_nonce_h * nonce);
109 #if NOT_USED
110 static int authDigestNonceLinks(digest_nonce_h * nonce);
111 #endif
112 static void authDigestNonceUserUnlink(digest_nonce_h * nonce);
113
114 static void
115 authDigestNonceEncode(digest_nonce_h * nonce)
116 {
117 if (!nonce)
118 return;
119
120 if (nonce->key)
121 xfree(nonce->key);
122
123 nonce->key = xstrdup(base64_encode_bin((char *) &(nonce->noncedata), sizeof(digest_nonce_data)));
124 }
125
126 static digest_nonce_h *
127 authenticateDigestNonceNew(void)
128 {
129 digest_nonce_h *newnonce = static_cast < digest_nonce_h * >(digest_nonce_pool->alloc());
130 digest_nonce_h *temp;
131
132 /* NONCE CREATION - NOTES AND REASONING. RBC 20010108
133 * === EXCERPT FROM RFC 2617 ===
134 * The contents of the nonce are implementation dependent. The quality
135 * of the implementation depends on a good choice. A nonce might, for
136 * example, be constructed as the base 64 encoding of
137 *
138 * time-stamp H(time-stamp ":" ETag ":" private-key)
139 *
140 * where time-stamp is a server-generated time or other non-repeating
141 * value, ETag is the value of the HTTP ETag header associated with
142 * the requested entity, and private-key is data known only to the
143 * server. With a nonce of this form a server would recalculate the
144 * hash portion after receiving the client authentication header and
145 * reject the request if it did not match the nonce from that header
146 * or if the time-stamp value is not recent enough. In this way the
147 * server can limit the time of the nonce's validity. The inclusion of
148 * the ETag prevents a replay request for an updated version of the
149 * resource. (Note: including the IP address of the client in the
150 * nonce would appear to offer the server the ability to limit the
151 * reuse of the nonce to the same client that originally got it.
152 * However, that would break proxy farms, where requests from a single
153 * user often go through different proxies in the farm. Also, IP
154 * address spoofing is not that hard.)
155 * ====
156 *
157 * Now for my reasoning:
158 * We will not accept a unrecognised nonce->we have all recognisable
159 * nonces stored. If we send out unique base64 encodings we guarantee
160 * that a given nonce applies to only one user (barring attacks or
161 * really bad timing with expiry and creation). Using a random
162 * component in the nonce allows us to loop to find a unique nonce.
163 * We use H(nonce_data) so the nonce is meaningless to the reciever.
164 * So our nonce looks like base64(H(timestamp,pointertohash,randomdata))
165 * And even if our randomness is not very random (probably due to
166 * bad coding on my part) we don't really care - the timestamp and
167 * memory pointer also guarantee local uniqueness in the input to the hash
168 * function.
169 */
170
171 /* create a new nonce */
172 newnonce->nc = 0;
173 newnonce->flags.valid = 1;
174 newnonce->noncedata.self = newnonce;
175 newnonce->noncedata.creationtime = current_time.tv_sec;
176 newnonce->noncedata.randomdata = squid_random();
177
178 authDigestNonceEncode(newnonce);
179 /*
180 * loop until we get a unique nonce. The nonce creation must
181 * have a random factor
182 */
183
184 while ((temp = authenticateDigestNonceFindNonce((char const *) (newnonce->key)))) {
185 /* create a new nonce */
186 newnonce->noncedata.randomdata = squid_random();
187 authDigestNonceEncode(newnonce);
188 }
189
190 hash_join(digest_nonce_cache, newnonce);
191 /* the cache's link */
192 authDigestNonceLink(newnonce);
193 newnonce->flags.incache = 1;
194 debugs(29, 5, "authenticateDigestNonceNew: created nonce " << newnonce << " at " << newnonce->noncedata.creationtime);
195 return newnonce;
196 }
197
198 static void
199 authenticateDigestNonceDelete(digest_nonce_h * nonce)
200 {
201 if (nonce) {
202 assert(nonce->references == 0);
203 #if UNREACHABLECODE
204
205 if (nonce->flags.incache)
206 hash_remove_link(digest_nonce_cache, nonce);
207
208 #endif
209
210 assert(nonce->flags.incache == 0);
211
212 safe_free(nonce->key);
213
214 digest_nonce_pool->freeOne(nonce);
215 }
216 }
217
218 static void
219 authenticateDigestNonceSetup(void)
220 {
221 if (!digest_nonce_pool)
222 digest_nonce_pool = memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h));
223
224 if (!digest_nonce_cache) {
225 digest_nonce_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string);
226 assert(digest_nonce_cache);
227 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->nonceGCInterval, 1);
228 }
229 }
230
231 void
232 authenticateDigestNonceShutdown(void)
233 {
234 /*
235 * We empty the cache of any nonces left in there.
236 */
237 digest_nonce_h *nonce;
238
239 if (digest_nonce_cache) {
240 debugs(29, 2, "authenticateDigestNonceShutdown: Shutting down nonce cache ");
241 hash_first(digest_nonce_cache);
242
243 while ((nonce = ((digest_nonce_h *) hash_next(digest_nonce_cache)))) {
244 assert(nonce->flags.incache);
245 authDigestNoncePurge(nonce);
246 }
247 }
248
249 #if DEBUGSHUTDOWN
250 if (digest_nonce_pool) {
251 delete digest_nonce_pool;
252 digest_nonce_pool = NULL;
253 }
254
255 #endif
256 debugs(29, 2, "authenticateDigestNonceShutdown: Nonce cache shutdown");
257 }
258
259 static void
260 authenticateDigestNonceCacheCleanup(void *data)
261 {
262 /*
263 * We walk the hash by nonceb64 as that is the unique key we
264 * use. For big hash tables we could consider stepping through
265 * the cache, 100/200 entries at a time. Lets see how it flies
266 * first.
267 */
268 digest_nonce_h *nonce;
269 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Cleaning the nonce cache now");
270 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Current time: " << current_time.tv_sec);
271 hash_first(digest_nonce_cache);
272
273 while ((nonce = ((digest_nonce_h *) hash_next(digest_nonce_cache)))) {
274 debugs(29, 3, "authenticateDigestNonceCacheCleanup: nonce entry : " << nonce << " '" << (char *) nonce->key << "'");
275 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Creation time: " << nonce->noncedata.creationtime);
276
277 if (authDigestNonceIsStale(nonce)) {
278 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Removing nonce " << (char *) nonce->key << " from cache due to timeout.");
279 assert(nonce->flags.incache);
280 /* invalidate nonce so future requests fail */
281 nonce->flags.valid = 0;
282 /* if it is tied to a auth_user, remove the tie */
283 authDigestNonceUserUnlink(nonce);
284 authDigestNoncePurge(nonce);
285 }
286 }
287
288 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Finished cleaning the nonce cache.");
289
290 if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->active())
291 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->nonceGCInterval, 1);
292 }
293
294 static void
295 authDigestNonceLink(digest_nonce_h * nonce)
296 {
297 assert(nonce != NULL);
298 nonce->references++;
299 debugs(29, 9, "authDigestNonceLink: nonce '" << nonce << "' now at '" << nonce->references << "'.");
300 }
301
302 #if NOT_USED
303 static int
304 authDigestNonceLinks(digest_nonce_h * nonce)
305 {
306 if (!nonce)
307 return -1;
308
309 return nonce->references;
310 }
311
312 #endif
313
314 void
315 authDigestNonceUnlink(digest_nonce_h * nonce)
316 {
317 assert(nonce != NULL);
318
319 if (nonce->references > 0) {
320 nonce->references--;
321 } else {
322 debugs(29, 1, "authDigestNonceUnlink; Attempt to lower nonce " << nonce << " refcount below 0!");
323 }
324
325 debugs(29, 9, "authDigestNonceUnlink: nonce '" << nonce << "' now at '" << nonce->references << "'.");
326
327 if (nonce->references == 0)
328 authenticateDigestNonceDelete(nonce);
329 }
330
331 const char *
332 authenticateDigestNonceNonceb64(const digest_nonce_h * nonce)
333 {
334 if (!nonce)
335 return NULL;
336
337 return (char const *) nonce->key;
338 }
339
340 static digest_nonce_h *
341 authenticateDigestNonceFindNonce(const char *nonceb64)
342 {
343 digest_nonce_h *nonce = NULL;
344
345 if (nonceb64 == NULL)
346 return NULL;
347
348 debugs(29, 9, "authDigestNonceFindNonce:looking for nonceb64 '" << nonceb64 << "' in the nonce cache.");
349
350 nonce = static_cast < digest_nonce_h * >(hash_lookup(digest_nonce_cache, nonceb64));
351
352 if ((nonce == NULL) || (strcmp(authenticateDigestNonceNonceb64(nonce), nonceb64)))
353 return NULL;
354
355 debugs(29, 9, "authDigestNonceFindNonce: Found nonce '" << nonce << "'");
356
357 return nonce;
358 }
359
360 int
361 authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9])
362 {
363 unsigned long intnc;
364 /* do we have a nonce ? */
365
366 if (!nonce)
367 return 0;
368
369 intnc = strtol(nc, NULL, 16);
370
371 /* has it already been invalidated ? */
372 if (!nonce->flags.valid) {
373 debugs(29, 4, "authDigestNonceIsValid: Nonce already invalidated");
374 return 0;
375 }
376
377 /* is the nonce-count ok ? */
378 if (!static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->CheckNonceCount) {
379 nonce->nc++;
380 return -1; /* forced OK by configuration */
381 }
382
383 if ((static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->NonceStrictness && intnc != nonce->nc + 1) ||
384 intnc < nonce->nc + 1) {
385 debugs(29, 4, "authDigestNonceIsValid: Nonce count doesn't match");
386 nonce->flags.valid = 0;
387 return 0;
388 }
389
390 /* seems ok */
391 /* increment the nonce count - we've already checked that intnc is a
392 * valid representation for us, so we don't need the test here.
393 */
394 nonce->nc = intnc;
395
396 return -1;
397 }
398
399 static int
400 authDigestNonceIsStale(digest_nonce_h * nonce)
401 {
402 /* do we have a nonce ? */
403
404 if (!nonce)
405 return -1;
406
407 /* has it's max duration expired? */
408 if (nonce->noncedata.creationtime + static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->noncemaxduration < current_time.tv_sec) {
409 debugs(29, 4, "authDigestNonceIsStale: Nonce is too old. " <<
410 nonce->noncedata.creationtime << " " <<
411 static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->noncemaxduration << " " <<
412 current_time.tv_sec);
413
414 nonce->flags.valid = 0;
415 return -1;
416 }
417
418 if (nonce->nc > 99999998) {
419 debugs(29, 4, "authDigestNonceIsStale: Nonce count overflow");
420 nonce->flags.valid = 0;
421 return -1;
422 }
423
424 if (nonce->nc > static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->noncemaxuses) {
425 debugs(29, 4, "authDigestNoncelastRequest: Nonce count over user limit");
426 nonce->flags.valid = 0;
427 return -1;
428 }
429
430 /* seems ok */
431 return 0;
432 }
433
434 /**
435 * \retval 0 the digest is not stale yet
436 * \retval -1 the digest will be stale on the next request
437 */
438 int
439 authDigestNonceLastRequest(digest_nonce_h * nonce)
440 {
441 if (!nonce)
442 return -1;
443
444 if (nonce->nc == 99999997) {
445 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to overflow");
446 return -1;
447 }
448
449 if (nonce->nc >= static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->noncemaxuses - 1) {
450 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to hit user limit");
451 return -1;
452 }
453
454 /* and other tests are possible. */
455 return 0;
456 }
457
458 void
459 authDigestNoncePurge(digest_nonce_h * nonce)
460 {
461 if (!nonce)
462 return;
463
464 if (!nonce->flags.incache)
465 return;
466
467 hash_remove_link(digest_nonce_cache, nonce);
468
469 nonce->flags.incache = 0;
470
471 /* the cache's link */
472 authDigestNonceUnlink(nonce);
473 }
474
475 /* USER related functions */
476 static Auth::User::Pointer
477 authDigestUserFindUsername(const char *username)
478 {
479 AuthUserHashPointer *usernamehash;
480 debugs(29, 9, HERE << "Looking for user '" << username << "'");
481
482 if (username && (usernamehash = static_cast < AuthUserHashPointer * >(hash_lookup(proxy_auth_username_cache, username)))) {
483 while ((usernamehash->user()->auth_type != Auth::AUTH_DIGEST) && (usernamehash->next))
484 usernamehash = static_cast<AuthUserHashPointer *>(usernamehash->next);
485
486 if (usernamehash->user()->auth_type == Auth::AUTH_DIGEST) {
487 return usernamehash->user();
488 }
489 }
490
491 return NULL;
492 }
493
494 void
495 Auth::Digest::Config::rotateHelpers()
496 {
497 /* schedule closure of existing helpers */
498 if (digestauthenticators) {
499 helperShutdown(digestauthenticators);
500 }
501
502 /* NP: dynamic helper restart will ensure they start up again as needed. */
503 }
504
505 void
506 Auth::Digest::Config::dump(StoreEntry * entry, const char *name, Auth::Config * scheme)
507 {
508 wordlist *list = authenticateProgram;
509 debugs(29, 9, "authDigestCfgDump: Dumping configuration");
510 storeAppendPrintf(entry, "%s %s", name, "digest");
511
512 while (list != NULL) {
513 storeAppendPrintf(entry, " %s", list->key);
514 list = list->next;
515 }
516
517 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",
518 name, "digest", digestAuthRealm,
519 name, "digest", authenticateChildren.n_max, authenticateChildren.n_startup, authenticateChildren.n_idle, authenticateChildren.concurrency,
520 name, "digest", noncemaxuses,
521 name, "digest", (int) noncemaxduration,
522 name, "digest", (int) nonceGCInterval);
523 }
524
525 bool
526 Auth::Digest::Config::active() const
527 {
528 return authdigest_initialised == 1;
529 }
530
531 bool
532 Auth::Digest::Config::configured() const
533 {
534 if ((authenticateProgram != NULL) &&
535 (authenticateChildren.n_max != 0) &&
536 (digestAuthRealm != NULL) && (noncemaxduration > -1))
537 return true;
538
539 return false;
540 }
541
542 /* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
543 void
544 Auth::Digest::Config::fixHeader(AuthUserRequest::Pointer auth_user_request, HttpReply *rep, http_hdr_type hdrType, HttpRequest * request)
545 {
546 if (!authenticateProgram)
547 return;
548
549 int stale = 0;
550
551 if (auth_user_request != NULL) {
552 AuthDigestUserRequest *digest_request;
553 digest_request = dynamic_cast<AuthDigestUserRequest*>(auth_user_request.getRaw());
554 assert (digest_request != NULL);
555
556 stale = !digest_request->flags.invalid_password;
557 }
558
559 /* on a 407 or 401 we always use a new nonce */
560 digest_nonce_h *nonce = authenticateDigestNonceNew();
561
562 debugs(29, 9, "authenticateFixHeader: Sending type:" << hdrType <<
563 " header: 'Digest realm=\"" << digestAuthRealm << "\", nonce=\"" <<
564 authenticateDigestNonceNonceb64(nonce) << "\", qop=\"" << QOP_AUTH <<
565 "\", stale=" << (stale ? "true" : "false"));
566
567 /* in the future, for WWW auth we may want to support the domain entry */
568 httpHeaderPutStrf(&rep->header, hdrType, "Digest realm=\"%s\", nonce=\"%s\", qop=\"%s\", stale=%s", digestAuthRealm, authenticateDigestNonceNonceb64(nonce), QOP_AUTH, stale ? "true" : "false");
569 }
570
571 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
572 * config file */
573 void
574 Auth::Digest::Config::init(Auth::Config * scheme)
575 {
576 if (authenticateProgram) {
577 DigestFieldsInfo = httpHeaderBuildFieldsInfo(DigestAttrs, DIGEST_ENUM_END);
578 authenticateDigestNonceSetup();
579 authdigest_initialised = 1;
580
581 if (digestauthenticators == NULL)
582 digestauthenticators = new helper("digestauthenticator");
583
584 digestauthenticators->cmdline = authenticateProgram;
585
586 digestauthenticators->childs = authenticateChildren;
587
588 digestauthenticators->ipc_type = IPC_STREAM;
589
590 helperOpenServers(digestauthenticators);
591 }
592 }
593
594 void
595 Auth::Digest::Config::registerWithCacheManager(void)
596 {
597 Mgr::RegisterAction("digestauthenticator",
598 "Digest User Authenticator Stats",
599 authenticateDigestStats, 0, 1);
600 }
601
602 /* free any allocated configuration details */
603 void
604 Auth::Digest::Config::done()
605 {
606 authdigest_initialised = 0;
607
608 if (digestauthenticators)
609 helperShutdown(digestauthenticators);
610
611 if (DigestFieldsInfo) {
612 httpHeaderDestroyFieldsInfo(DigestFieldsInfo, DIGEST_ENUM_END);
613 DigestFieldsInfo = NULL;
614 }
615
616 if (!shutting_down)
617 return;
618
619 delete digestauthenticators;
620 digestauthenticators = NULL;
621
622 if (authenticateProgram)
623 wordlistDestroy(&authenticateProgram);
624
625 safe_free(digestAuthRealm);
626 }
627
628 Auth::Digest::Config::Config() :
629 digestAuthRealm(NULL),
630 nonceGCInterval(5*60),
631 noncemaxduration(30*60),
632 noncemaxuses(50),
633 NonceStrictness(0),
634 CheckNonceCount(1),
635 PostWorkaround(0),
636 utf8(0)
637 {}
638
639 void
640 Auth::Digest::Config::parse(Auth::Config * scheme, int n_configured, char *param_str)
641 {
642 if (strcasecmp(param_str, "program") == 0) {
643 if (authenticateProgram)
644 wordlistDestroy(&authenticateProgram);
645
646 parse_wordlist(&authenticateProgram);
647
648 requirePathnameExists("auth_param digest program", authenticateProgram->key);
649 } else if (strcasecmp(param_str, "children") == 0) {
650 authenticateChildren.parseConfig();
651 } else if (strcasecmp(param_str, "realm") == 0) {
652 parse_eol(&digestAuthRealm);
653 } else if (strcasecmp(param_str, "nonce_garbage_interval") == 0) {
654 parse_time_t(&nonceGCInterval);
655 } else if (strcasecmp(param_str, "nonce_max_duration") == 0) {
656 parse_time_t(&noncemaxduration);
657 } else if (strcasecmp(param_str, "nonce_max_count") == 0) {
658 parse_int((int *) &noncemaxuses);
659 } else if (strcasecmp(param_str, "nonce_strictness") == 0) {
660 parse_onoff(&NonceStrictness);
661 } else if (strcasecmp(param_str, "check_nonce_count") == 0) {
662 parse_onoff(&CheckNonceCount);
663 } else if (strcasecmp(param_str, "post_workaround") == 0) {
664 parse_onoff(&PostWorkaround);
665 } else if (strcasecmp(param_str, "utf8") == 0) {
666 parse_onoff(&utf8);
667 } else {
668 debugs(29, 0, "unrecognised digest auth scheme parameter '" << param_str << "'");
669 }
670 }
671
672 const char *
673 Auth::Digest::Config::type() const
674 {
675 return Auth::Digest::Scheme::GetInstance()->type();
676 }
677
678
679 static void
680 authenticateDigestStats(StoreEntry * sentry)
681 {
682 helperStats(sentry, digestauthenticators, "Digest Authenticator Statistics");
683 }
684
685 /* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
686
687 static void
688 authDigestNonceUserUnlink(digest_nonce_h * nonce)
689 {
690 Auth::Digest::User *digest_user;
691 dlink_node *link, *tmplink;
692
693 if (!nonce)
694 return;
695
696 if (!nonce->user)
697 return;
698
699 digest_user = nonce->user;
700
701 /* unlink from the user list. Yes we're crossing structures but this is the only
702 * time this code is needed
703 */
704 link = digest_user->nonces.head;
705
706 while (link) {
707 tmplink = link;
708 link = link->next;
709
710 if (tmplink->data == nonce) {
711 dlinkDelete(tmplink, &digest_user->nonces);
712 authDigestNonceUnlink(static_cast < digest_nonce_h * >(tmplink->data));
713 dlinkNodeDelete(tmplink);
714 link = NULL;
715 }
716 }
717
718 /* this reference to user was not locked because freeeing the user frees
719 * the nonce too.
720 */
721 nonce->user = NULL;
722 }
723
724 /* authDigestUserLinkNonce: add a nonce to a given user's struct */
725 static void
726 authDigestUserLinkNonce(Auth::Digest::User * user, digest_nonce_h * nonce)
727 {
728 dlink_node *node;
729
730 if (!user || !nonce)
731 return;
732
733 Auth::Digest::User *digest_user = user;
734
735 node = digest_user->nonces.head;
736
737 while (node && (node->data != nonce))
738 node = node->next;
739
740 if (node)
741 return;
742
743 node = dlinkNodeNew();
744
745 dlinkAddTail(nonce, node, &digest_user->nonces);
746
747 authDigestNonceLink(nonce);
748
749 /* ping this nonce to this auth user */
750 assert((nonce->user == NULL) || (nonce->user == user));
751
752 /* we don't lock this reference because removing the user removes the
753 * hash too. Of course if that changes we're stuffed so read the code huh?
754 */
755 nonce->user = user;
756 }
757
758 /* setup the necessary info to log the username */
759 static AuthUserRequest::Pointer
760 authDigestLogUsername(char *username, AuthUserRequest::Pointer auth_user_request)
761 {
762 assert(auth_user_request != NULL);
763
764 /* log the username */
765 debugs(29, 9, "authDigestLogUsername: Creating new user for logging '" << username << "'");
766 Auth::User::Pointer digest_user = new Auth::Digest::User(static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest")));
767 /* save the credentials */
768 digest_user->username(username);
769 /* set the auth_user type */
770 digest_user->auth_type = Auth::AUTH_BROKEN;
771 /* link the request to the user */
772 auth_user_request->user(digest_user);
773 return auth_user_request;
774 }
775
776 /*
777 * Decode a Digest [Proxy-]Auth string, placing the results in the passed
778 * Auth_user structure.
779 */
780 AuthUserRequest::Pointer
781 Auth::Digest::Config::decode(char const *proxy_auth)
782 {
783 const char *item;
784 const char *p;
785 const char *pos = NULL;
786 char *username = NULL;
787 digest_nonce_h *nonce;
788 int ilen;
789
790 debugs(29, 9, "authenticateDigestDecodeAuth: beginning");
791
792 AuthDigestUserRequest *digest_request = new AuthDigestUserRequest();
793
794 /* trim DIGEST from string */
795
796 while (xisgraph(*proxy_auth))
797 proxy_auth++;
798
799 /* Trim leading whitespace before decoding */
800 while (xisspace(*proxy_auth))
801 proxy_auth++;
802
803 String temp(proxy_auth);
804
805 while (strListGetItem(&temp, ',', &item, &ilen, &pos)) {
806 /* isolate directive name & value */
807 size_t nlen;
808 size_t vlen;
809 if ((p = (const char *)memchr(item, '=', ilen)) && (p - item < ilen)) {
810 nlen = p++ - item;
811 vlen = ilen - (p - item);
812 } else {
813 nlen = ilen;
814 vlen = 0;
815 }
816
817 /* parse value. auth-param = token "=" ( token | quoted-string ) */
818 String value;
819 if (vlen > 0) {
820 if (*p == '"') {
821 if (!httpHeaderParseQuotedString(p, vlen, &value)) {
822 debugs(29, 9, "authDigestDecodeAuth: Failed to parse attribute '" << item << "' in '" << temp << "'");
823 continue;
824 }
825 } else {
826 value.limitInit(p, vlen);
827 }
828 } else {
829 debugs(29, 9, "authDigestDecodeAuth: Failed to parse attribute '" << item << "' in '" << temp << "'");
830 continue;
831 }
832
833 /* find type */
834 http_digest_attr_type type = (http_digest_attr_type)httpHeaderIdByName(item, nlen, DigestFieldsInfo, DIGEST_ENUM_END);
835
836 switch (type) {
837 case DIGEST_USERNAME:
838 safe_free(username);
839 username = xstrndup(value.rawBuf(), value.size() + 1);
840 debugs(29, 9, "authDigestDecodeAuth: Found Username '" << username << "'");
841 break;
842
843 case DIGEST_REALM:
844 safe_free(digest_request->realm);
845 digest_request->realm = xstrndup(value.rawBuf(), value.size() + 1);
846 debugs(29, 9, "authDigestDecodeAuth: Found realm '" << digest_request->realm << "'");
847 break;
848
849 case DIGEST_QOP:
850 safe_free(digest_request->qop);
851 digest_request->qop = xstrndup(value.rawBuf(), value.size() + 1);
852 debugs(29, 9, "authDigestDecodeAuth: Found qop '" << digest_request->qop << "'");
853 break;
854
855 case DIGEST_ALGORITHM:
856 safe_free(digest_request->algorithm);
857 digest_request->algorithm = xstrndup(value.rawBuf(), value.size() + 1);
858 debugs(29, 9, "authDigestDecodeAuth: Found algorithm '" << digest_request->algorithm << "'");
859 break;
860
861 case DIGEST_URI:
862 safe_free(digest_request->uri);
863 digest_request->uri = xstrndup(value.rawBuf(), value.size() + 1);
864 debugs(29, 9, "authDigestDecodeAuth: Found uri '" << digest_request->uri << "'");
865 break;
866
867 case DIGEST_NONCE:
868 safe_free(digest_request->nonceb64);
869 digest_request->nonceb64 = xstrndup(value.rawBuf(), value.size() + 1);
870 debugs(29, 9, "authDigestDecodeAuth: Found nonce '" << digest_request->nonceb64 << "'");
871 break;
872
873 case DIGEST_NC:
874 if (value.size() != 8) {
875 debugs(29, 9, "authDigestDecodeAuth: Invalid nc '" << value << "' in '" << temp << "'");
876 }
877 xstrncpy(digest_request->nc, value.rawBuf(), value.size() + 1);
878 debugs(29, 9, "authDigestDecodeAuth: Found noncecount '" << digest_request->nc << "'");
879 break;
880
881 case DIGEST_CNONCE:
882 safe_free(digest_request->cnonce);
883 digest_request->cnonce = xstrndup(value.rawBuf(), value.size() + 1);
884 debugs(29, 9, "authDigestDecodeAuth: Found cnonce '" << digest_request->cnonce << "'");
885 break;
886
887 case DIGEST_RESPONSE:
888 safe_free(digest_request->response);
889 digest_request->response = xstrndup(value.rawBuf(), value.size() + 1);
890 debugs(29, 9, "authDigestDecodeAuth: Found response '" << digest_request->response << "'");
891 break;
892
893 default:
894 debugs(29, 3, "authDigestDecodeAuth: Unknown attribute '" << item << "' in '" << temp << "'");
895 break;
896 }
897 }
898
899 temp.clean();
900
901
902 /* now we validate the data given to us */
903
904 /*
905 * TODO: on invalid parameters we should return 400, not 407.
906 * Find some clean way of doing this. perhaps return a valid
907 * struct, and set the direction to clientwards combined with
908 * a change to the clientwards handling code (ie let the
909 * clientwards call set the error type (but limited to known
910 * correct values - 400/401/407
911 */
912
913 /* 2069 requirements */
914
915 /* do we have a username ? */
916 if (!username || username[0] == '\0') {
917 debugs(29, 2, "authenticateDigestDecode: Empty or not present username");
918 return authDigestLogUsername(username, digest_request);
919 }
920
921 /* Sanity check of the username.
922 * " can not be allowed in usernames until * the digest helper protocol
923 * have been redone
924 */
925 if (strchr(username, '"')) {
926 debugs(29, 2, "authenticateDigestDecode: Unacceptable username '" << username << "'");
927 return authDigestLogUsername(username, digest_request);
928 }
929
930 /* do we have a realm ? */
931 if (!digest_request->realm || digest_request->realm[0] == '\0') {
932 debugs(29, 2, "authenticateDigestDecode: Empty or not present realm");
933 return authDigestLogUsername(username, digest_request);
934 }
935
936 /* and a nonce? */
937 if (!digest_request->nonceb64 || digest_request->nonceb64[0] == '\0') {
938 debugs(29, 2, "authenticateDigestDecode: Empty or not present nonce");
939 return authDigestLogUsername(username, digest_request);
940 }
941
942 /* we can't check the URI just yet. We'll check it in the
943 * authenticate phase, but needs to be given */
944 if (!digest_request->uri || digest_request->uri[0] == '\0') {
945 debugs(29, 2, "authenticateDigestDecode: Missing URI field");
946 return authDigestLogUsername(username, digest_request);
947 }
948
949 /* is the response the correct length? */
950 if (!digest_request->response || strlen(digest_request->response) != 32) {
951 debugs(29, 2, "authenticateDigestDecode: Response length invalid");
952 return authDigestLogUsername(username, digest_request);
953 }
954
955 /* check the algorithm is present and supported */
956 if (!digest_request->algorithm)
957 digest_request->algorithm = xstrndup("MD5", 4);
958 else if (strcmp(digest_request->algorithm, "MD5")
959 && strcmp(digest_request->algorithm, "MD5-sess")) {
960 debugs(29, 2, "authenticateDigestDecode: invalid algorithm specified!");
961 return authDigestLogUsername(username, digest_request);
962 }
963
964 /* 2617 requirements, indicated by qop */
965 if (digest_request->qop) {
966
967 /* check the qop is what we expected. */
968 if (strcmp(digest_request->qop, QOP_AUTH) != 0) {
969 /* we received a qop option we didn't send */
970 debugs(29, 2, "authenticateDigestDecode: Invalid qop option received");
971 return authDigestLogUsername(username, digest_request);
972 }
973
974 /* check cnonce */
975 if (!digest_request->cnonce || digest_request->cnonce[0] == '\0') {
976 debugs(29, 2, "authenticateDigestDecode: Missing cnonce field");
977 return authDigestLogUsername(username, digest_request);
978 }
979
980 /* check nc */
981 if (strlen(digest_request->nc) != 8 || strspn(digest_request->nc, "0123456789abcdefABCDEF") != 8) {
982 debugs(29, 2, "authenticateDigestDecode: invalid nonce count");
983 return authDigestLogUsername(username, digest_request);
984 }
985 } else {
986 /* cnonce and nc both require qop */
987 if (digest_request->cnonce || digest_request->nc) {
988 debugs(29, 2, "authenticateDigestDecode: missing qop!");
989 return authDigestLogUsername(username, digest_request);
990 }
991 }
992
993 /** below nonce state dependent **/
994
995 /* now the nonce */
996 nonce = authenticateDigestNonceFindNonce(digest_request->nonceb64);
997 if (!nonce) {
998 /* we couldn't find a matching nonce! */
999 debugs(29, 2, "authenticateDigestDecode: Unexpected or invalid nonce received");
1000 if (digest_request->user() != NULL)
1001 digest_request->user()->credentials(Auth::Failed);
1002 return authDigestLogUsername(username, digest_request);
1003 }
1004
1005 digest_request->nonce = nonce;
1006 authDigestNonceLink(nonce);
1007
1008 /* check that we're not being hacked / the username hasn't changed */
1009 if (nonce->user && strcmp(username, nonce->user->username())) {
1010 debugs(29, 2, "authenticateDigestDecode: Username for the nonce does not equal the username for the request");
1011 return authDigestLogUsername(username, digest_request);
1012 }
1013
1014 /* the method we'll check at the authenticate step as well */
1015
1016
1017 /* we don't send or parse opaques. Ok so we're flexable ... */
1018
1019 /* find the user */
1020 Auth::Digest::User *digest_user;
1021
1022 Auth::User::Pointer auth_user;
1023
1024 if ((auth_user = authDigestUserFindUsername(username)) == NULL) {
1025 /* the user doesn't exist in the username cache yet */
1026 debugs(29, 9, "authDigestDecodeAuth: Creating new digest user '" << username << "'");
1027 digest_user = new Auth::Digest::User(this);
1028 /* auth_user is a parent */
1029 auth_user = digest_user;
1030 /* save the username */
1031 digest_user->username(username);
1032 /* set the user type */
1033 digest_user->auth_type = Auth::AUTH_DIGEST;
1034 /* this auth_user struct is the one to get added to the
1035 * username cache */
1036 /* store user in hash's */
1037 digest_user->addToNameCache();
1038
1039 /*
1040 * Add the digest to the user so we can tell if a hacking
1041 * or spoofing attack is taking place. We do this by assuming
1042 * the user agent won't change user name without warning.
1043 */
1044 authDigestUserLinkNonce(digest_user, nonce);
1045 } else {
1046 debugs(29, 9, "authDigestDecodeAuth: Found user '" << username << "' in the user cache as '" << auth_user << "'");
1047 digest_user = static_cast<Auth::Digest::User *>(auth_user.getRaw());
1048 xfree(username);
1049 }
1050
1051 /*link the request and the user */
1052 assert(digest_request != NULL);
1053
1054 digest_request->user(digest_user);
1055 debugs(29, 9, "username = '" << digest_user->username() << "'\nrealm = '" <<
1056 digest_request->realm << "'\nqop = '" << digest_request->qop <<
1057 "'\nalgorithm = '" << digest_request->algorithm << "'\nuri = '" <<
1058 digest_request->uri << "'\nnonce = '" << digest_request->nonceb64 <<
1059 "'\nnc = '" << digest_request->nc << "'\ncnonce = '" <<
1060 digest_request->cnonce << "'\nresponse = '" <<
1061 digest_request->response << "'\ndigestnonce = '" << nonce << "'");
1062
1063 return digest_request;
1064 }