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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors