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