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