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"
16 #include "auth/CredentialsCache.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 
53 static int authdigest_initialised = 0;
55 
67 };
68 
69 static 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 
94 static void authenticateDigestNonceCacheCleanup(void *data);
95 static digest_nonce_h *authenticateDigestNonceFindNonce(const char *noncehex);
96 static void authenticateDigestNonceDelete(digest_nonce_h * nonce);
97 static void authenticateDigestNonceSetup(void);
98 static void authDigestNonceEncode(digest_nonce_h * nonce);
99 static void authDigestNonceLink(digest_nonce_h * nonce);
100 static void authDigestNonceUserUnlink(digest_nonce_h * nonce);
101 
102 static void
103 authDigestNonceEncode(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 
121 digest_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 
189 static void
190 authenticateDigestNonceDelete(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 
198  digest_nonce_pool->freeOne(nonce);
199  }
200 }
201 
202 static void
204 {
205  if (!digest_nonce_pool)
206  digest_nonce_pool = memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h));
207 
208  if (!digest_nonce_cache) {
209  digest_nonce_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string);
211  eventAdd("Digest nonce cache maintenance", authenticateDigestNonceCacheCleanup, nullptr, static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->nonceGCInterval, 1);
212  }
213 }
214 
215 void
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);
229  authDigestNoncePurge(nonce);
230  }
231  }
232 
233  debugs(29, 2, "Nonce cache shutdown");
234 }
235 
236 static 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 */
261  authDigestNoncePurge(nonce);
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 
271 static void
272 authDigestNonceLink(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 
280 void
281 authDigestNonceUnlink(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 
297 const char *
298 authenticateDigestNonceNonceHex(const digest_nonce_h * nonce)
299 {
300  if (!nonce)
301  return nullptr;
302 
303  return (char const *) nonce->key;
304 }
305 
306 static digest_nonce_h *
307 authenticateDigestNonceFindNonce(const char *noncehex)
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 
326 int
327 authDigestNonceIsValid(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 
364 int
365 authDigestNonceIsStale(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 
407 int
408 authDigestNonceLastRequest(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 
427 void
428 authDigestNoncePurge(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 */
441  authDigestNonceUnlink(nonce);
442 }
443 
444 void
445 Auth::Digest::Config::rotateHelpers()
446 {
447  /* schedule closure of existing helpers */
448  if (digestauthenticators) {
450  }
451 
452  /* NP: dynamic helper restart will ensure they start up again as needed. */
453 }
454 
455 bool
456 Auth::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 
468 bool
469 Auth::Digest::Config::active() const
470 {
471  return authdigest_initialised == 1;
472 }
473 
474 bool
475 Auth::Digest::Config::configured() const
476 {
477  if ((authenticateProgram != nullptr) &&
478  (authenticateChildren.n_max != 0) &&
479  !realm.isEmpty() && (noncemaxduration > -1))
480  return true;
481 
482  return false;
483 }
484 
485 /* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
486 void
487 Auth::Digest::Config::fixHeader(Auth::UserRequest::Pointer auth_user_request, HttpReply *rep, Http::HdrType hdrType, HttpRequest *)
488 {
489  if (!authenticateProgram)
490  return;
491 
492  bool stale = false;
493  digest_nonce_h *nonce = nullptr;
494 
495  /* on a 407 or 401 we always use a new nonce */
496  if (auth_user_request != nullptr) {
497  Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
498 
499  if (digest_user) {
500  stale = digest_user->credentials() == Auth::Handshake;
501  if (stale) {
502  nonce = digest_user->currentNonce();
503  }
504  }
505  }
506  if (!nonce) {
507  nonce = authenticateDigestNonceNew();
508  }
509 
510  debugs(29, 9, "Sending type:" << hdrType <<
511  " header: 'Digest realm=\"" << realm << "\", nonce=\"" <<
512  authenticateDigestNonceNonceHex(nonce) << "\", qop=\"" << QOP_AUTH <<
513  "\", stale=" << (stale ? "true" : "false"));
514 
515  /* in the future, for WWW auth we may want to support the domain entry */
516  httpHeaderPutStrf(&rep->header, hdrType, "Digest realm=\"" SQUIDSBUFPH "\", nonce=\"%s\", qop=\"%s\", stale=%s",
517  SQUIDSBUFPRINT(realm), authenticateDigestNonceNonceHex(nonce), QOP_AUTH, stale ? "true" : "false");
518 }
519 
520 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
521  * config file */
522 void
523 Auth::Digest::Config::init(Auth::SchemeConfig *)
524 {
525  if (authenticateProgram) {
528 
529  if (digestauthenticators == nullptr)
530  digestauthenticators = Helper::Client::Make("digestauthenticator");
531 
532  digestauthenticators->cmdline = authenticateProgram;
533 
534  digestauthenticators->childs.updateLimits(authenticateChildren);
535 
536  digestauthenticators->ipc_type = IPC_STREAM;
537 
538  digestauthenticators->openSessions();
539  }
540 }
541 
542 void
543 Auth::Digest::Config::registerWithCacheManager(void)
544 {
545  Mgr::RegisterAction("digestauthenticator",
546  "Digest User Authenticator Stats",
548 }
549 
550 /* free any allocated configuration details */
551 void
552 Auth::Digest::Config::done()
553 {
555 
557 
560 
561  if (!shutting_down)
562  return;
563 
564  digestauthenticators = nullptr;
565 
566  if (authenticateProgram)
567  wordlistDestroy(&authenticateProgram);
568 }
569 
571  nonceGCInterval(5*60),
572  noncemaxduration(30*60),
573  noncemaxuses(50),
574  NonceStrictness(0),
575  CheckNonceCount(1),
576  PostWorkaround(0)
577 {}
578 
579 void
580 Auth::Digest::Config::parse(Auth::SchemeConfig * scheme, int n_configured, char *param_str)
581 {
582  if (strcmp(param_str, "nonce_garbage_interval") == 0) {
583  parse_time_t(&nonceGCInterval);
584  } else if (strcmp(param_str, "nonce_max_duration") == 0) {
585  parse_time_t(&noncemaxduration);
586  } else if (strcmp(param_str, "nonce_max_count") == 0) {
587  parse_int((int *) &noncemaxuses);
588  } else if (strcmp(param_str, "nonce_strictness") == 0) {
589  parse_onoff(&NonceStrictness);
590  } else if (strcmp(param_str, "check_nonce_count") == 0) {
591  parse_onoff(&CheckNonceCount);
592  } else if (strcmp(param_str, "post_workaround") == 0) {
593  parse_onoff(&PostWorkaround);
594  } else
595  Auth::SchemeConfig::parse(scheme, n_configured, param_str);
596 }
597 
598 const char *
599 Auth::Digest::Config::type() const
600 {
601  return Auth::Digest::Scheme::GetInstance()->type();
602 }
603 
604 static void
606 {
608  digestauthenticators->packStatsInto(sentry, "Digest Authenticator Statistics");
609 }
610 
611 /* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
612 
613 static void
614 authDigestNonceUserUnlink(digest_nonce_h * nonce)
615 {
616  Auth::Digest::User *digest_user;
617  dlink_node *link, *tmplink;
618 
619  if (!nonce)
620  return;
621 
622  if (!nonce->user)
623  return;
624 
625  digest_user = nonce->user;
626 
627  /* unlink from the user list. Yes we're crossing structures but this is the only
628  * time this code is needed
629  */
630  link = digest_user->nonces.head;
631 
632  while (link) {
633  tmplink = link;
634  link = link->next;
635 
636  if (tmplink->data == nonce) {
637  dlinkDelete(tmplink, &digest_user->nonces);
638  authDigestNonceUnlink(static_cast < digest_nonce_h * >(tmplink->data));
639  delete tmplink;
640  link = nullptr;
641  }
642  }
643 
644  /* this reference to user was not locked because freeeing the user frees
645  * the nonce too.
646  */
647  nonce->user = nullptr;
648 }
649 
650 /* authDigesteserLinkNonce: add a nonce to a given user's struct */
651 void
652 authDigestUserLinkNonce(Auth::Digest::User * user, digest_nonce_h * nonce)
653 {
654  dlink_node *node;
655 
656  if (!user || !nonce || !nonce->user)
657  return;
658 
659  Auth::Digest::User *digest_user = user;
660 
661  node = digest_user->nonces.head;
662 
663  while (node && (node->data != nonce))
664  node = node->next;
665 
666  if (node)
667  return;
668 
669  node = new dlink_node;
670 
671  dlinkAddTail(nonce, node, &digest_user->nonces);
672 
673  authDigestNonceLink(nonce);
674 
675  /* ping this nonce to this auth user */
676  assert((nonce->user == nullptr) || (nonce->user == user));
677 
678  /* we don't lock this reference because removing the user removes the
679  * hash too. Of course if that changes we're stuffed so read the code huh?
680  */
681  nonce->user = user;
682 }
683 
684 /* setup the necessary info to log the username */
686 authDigestLogUsername(char *username, Auth::UserRequest::Pointer auth_user_request, const char *requestRealm)
687 {
688  assert(auth_user_request != nullptr);
689 
690  /* log the username */
691  debugs(29, 9, "Creating new user for logging '" << (username?username:"[no username]") << "'");
692  Auth::User::Pointer digest_user = new Auth::Digest::User(static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest")), requestRealm);
693  /* save the credentials */
694  digest_user->username(username);
695  /* set the auth_user type */
696  digest_user->auth_type = Auth::AUTH_BROKEN;
697  /* link the request to the user */
698  auth_user_request->user(digest_user);
699  return auth_user_request;
700 }
701 
702 /*
703  * Decode a Digest [Proxy-]Auth string, placing the results in the passed
704  * Auth_user structure.
705  */
707 Auth::Digest::Config::decode(char const *proxy_auth, const HttpRequest *request, const char *aRequestRealm)
708 {
709  const char *item;
710  const char *p;
711  const char *pos = nullptr;
712  char *username = nullptr;
713  digest_nonce_h *nonce;
714  int ilen;
715 
716  debugs(29, 9, "beginning");
717 
718  Auth::Digest::UserRequest *digest_request = new Auth::Digest::UserRequest();
719 
720  /* trim DIGEST from string */
721 
722  while (xisgraph(*proxy_auth))
723  ++proxy_auth;
724 
725  /* Trim leading whitespace before decoding */
726  while (xisspace(*proxy_auth))
727  ++proxy_auth;
728 
729  String temp(proxy_auth);
730 
731  while (strListGetItem(&temp, ',', &item, &ilen, &pos)) {
732  /* isolate directive name & value */
733  size_t nlen;
734  size_t vlen;
735  if ((p = (const char *)memchr(item, '=', ilen)) && (p - item < ilen)) {
736  nlen = p - item;
737  ++p;
738  vlen = ilen - (p - item);
739  } else {
740  nlen = ilen;
741  vlen = 0;
742  }
743 
744  SBuf keyName(item, nlen);
745  String value;
746 
747  if (vlen > 0) {
748  // see RFC 2617 section 3.2.1 and 3.2.2 for details on the BNF
749 
750  if (keyName == SBuf("domain",6) || keyName == SBuf("uri",3)) {
751  // domain is Special. Not a quoted-string, must not be de-quoted. But is wrapped in '"'
752  // BUG 3077: uri= can also be sent to us in a mangled (invalid!) form like domain
753  if (vlen > 1 && *p == '"' && *(p + vlen -1) == '"') {
754  value.assign(p+1, vlen-2);
755  }
756  } else if (keyName == SBuf("qop",3)) {
757  // qop is more special.
758  // On request this must not be quoted-string de-quoted. But is several values wrapped in '"'
759  // On response this is a single un-quoted token.
760  if (vlen > 1 && *p == '"' && *(p + vlen -1) == '"') {
761  value.assign(p+1, vlen-2);
762  } else {
763  value.assign(p, vlen);
764  }
765  } else if (*p == '"') {
766  if (!httpHeaderParseQuotedString(p, vlen, &value)) {
767  debugs(29, 9, "Failed to parse attribute '" << item << "' in '" << temp << "'");
768  continue;
769  }
770  } else {
771  value.assign(p, vlen);
772  }
773  } else {
774  debugs(29, 9, "Failed to parse attribute '" << item << "' in '" << temp << "'");
775  continue;
776  }
777 
778  /* find type */
779  const auto t = digestFieldsLookupTable().lookup(keyName);
780 
781  switch (t) {
782  case DIGEST_USERNAME:
783  safe_free(username);
784  if (value.size() != 0) {
785  const auto v = value.termedBuf();
786  if (utf8 && !isValidUtf8String(v, v + value.size())) {
787  auto str = isCP1251EncodingAllowed(request) ? Cp1251ToUtf8(v) : Latin1ToUtf8(v);
788  value = SBufToString(str);
789  }
790  username = xstrndup(value.rawBuf(), value.size() + 1);
791  }
792  debugs(29, 9, "Found Username '" << username << "'");
793  break;
794 
795  case DIGEST_REALM:
796  safe_free(digest_request->realm);
797  if (value.size() != 0)
798  digest_request->realm = xstrndup(value.rawBuf(), value.size() + 1);
799  debugs(29, 9, "Found realm '" << digest_request->realm << "'");
800  break;
801 
802  case DIGEST_QOP:
803  safe_free(digest_request->qop);
804  if (value.size() != 0)
805  digest_request->qop = xstrndup(value.rawBuf(), value.size() + 1);
806  debugs(29, 9, "Found qop '" << digest_request->qop << "'");
807  break;
808 
809  case DIGEST_ALGORITHM:
810  safe_free(digest_request->algorithm);
811  if (value.size() != 0)
812  digest_request->algorithm = xstrndup(value.rawBuf(), value.size() + 1);
813  debugs(29, 9, "Found algorithm '" << digest_request->algorithm << "'");
814  break;
815 
816  case DIGEST_URI:
817  safe_free(digest_request->uri);
818  if (value.size() != 0)
819  digest_request->uri = xstrndup(value.rawBuf(), value.size() + 1);
820  debugs(29, 9, "Found uri '" << digest_request->uri << "'");
821  break;
822 
823  case DIGEST_NONCE:
824  safe_free(digest_request->noncehex);
825  if (value.size() != 0)
826  digest_request->noncehex = xstrndup(value.rawBuf(), value.size() + 1);
827  debugs(29, 9, "Found nonce '" << digest_request->noncehex << "'");
828  break;
829 
830  case DIGEST_NC:
831  if (value.size() == 8) {
832  // for historical reasons, the nc value MUST be exactly 8 bytes
833  static_assert(sizeof(digest_request->nc) == 8 + 1);
834  xstrncpy(digest_request->nc, value.rawBuf(), value.size() + 1);
835  debugs(29, 9, "Found noncecount '" << digest_request->nc << "'");
836  } else {
837  debugs(29, 9, "Invalid nc '" << value << "' in '" << temp << "'");
838  digest_request->nc[0] = 0;
839  }
840  break;
841 
842  case DIGEST_CNONCE:
843  safe_free(digest_request->cnonce);
844  if (value.size() != 0)
845  digest_request->cnonce = xstrndup(value.rawBuf(), value.size() + 1);
846  debugs(29, 9, "Found cnonce '" << digest_request->cnonce << "'");
847  break;
848 
849  case DIGEST_RESPONSE:
850  safe_free(digest_request->response);
851  if (value.size() != 0)
852  digest_request->response = xstrndup(value.rawBuf(), value.size() + 1);
853  debugs(29, 9, "Found response '" << digest_request->response << "'");
854  break;
855 
856  default:
857  debugs(29, 3, "Unknown attribute '" << item << "' in '" << temp << "'");
858  break;
859  }
860  }
861 
862  temp.clean();
863 
864  /* now we validate the data given to us */
865 
866  /*
867  * TODO: on invalid parameters we should return 400, not 407.
868  * Find some clean way of doing this. perhaps return a valid
869  * struct, and set the direction to clientwards combined with
870  * a change to the clientwards handling code (ie let the
871  * clientwards call set the error type (but limited to known
872  * correct values - 400/401/407
873  */
874 
875  /* 2069 requirements */
876 
877  // return value.
879  /* do we have a username ? */
880  if (!username || username[0] == '\0') {
881  debugs(29, 2, "Empty or not present username");
882  rv = authDigestLogUsername(username, digest_request, aRequestRealm);
883  safe_free(username);
884  return rv;
885  }
886 
887  /* Sanity check of the username.
888  * " can not be allowed in usernames until * the digest helper protocol
889  * have been redone
890  */
891  if (strchr(username, '"')) {
892  debugs(29, 2, "Unacceptable username '" << username << "'");
893  rv = authDigestLogUsername(username, digest_request, aRequestRealm);
894  safe_free(username);
895  return rv;
896  }
897 
898  /* do we have a realm ? */
899  if (!digest_request->realm || digest_request->realm[0] == '\0') {
900  debugs(29, 2, "Empty or not present realm");
901  rv = authDigestLogUsername(username, digest_request, aRequestRealm);
902  safe_free(username);
903  return rv;
904  }
905 
906  /* and a nonce? */
907  if (!digest_request->noncehex || digest_request->noncehex[0] == '\0') {
908  debugs(29, 2, "Empty or not present nonce");
909  rv = authDigestLogUsername(username, digest_request, aRequestRealm);
910  safe_free(username);
911  return rv;
912  }
913 
914  /* we can't check the URI just yet. We'll check it in the
915  * authenticate phase, but needs to be given */
916  if (!digest_request->uri || digest_request->uri[0] == '\0') {
917  debugs(29, 2, "Missing URI field");
918  rv = authDigestLogUsername(username, digest_request, aRequestRealm);
919  safe_free(username);
920  return rv;
921  }
922 
923  /* is the response the correct length? */
924  if (!digest_request->response || strlen(digest_request->response) != 32) {
925  debugs(29, 2, "Response length invalid");
926  rv = authDigestLogUsername(username, digest_request, aRequestRealm);
927  safe_free(username);
928  return rv;
929  }
930 
931  /* check the algorithm is present and supported */
932  if (!digest_request->algorithm)
933  digest_request->algorithm = xstrndup("MD5", 4);
934  else if (strcmp(digest_request->algorithm, "MD5")
935  && strcmp(digest_request->algorithm, "MD5-sess")) {
936  debugs(29, 2, "invalid algorithm specified!");
937  rv = authDigestLogUsername(username, digest_request, aRequestRealm);
938  safe_free(username);
939  return rv;
940  }
941 
942  /* 2617 requirements, indicated by qop */
943  if (digest_request->qop) {
944 
945  /* check the qop is what we expected. */
946  if (strcmp(digest_request->qop, QOP_AUTH) != 0) {
947  /* we received a qop option we didn't send */
948  debugs(29, 2, "Invalid qop option received");
949  rv = authDigestLogUsername(username, digest_request, aRequestRealm);
950  safe_free(username);
951  return rv;
952  }
953 
954  /* check cnonce */
955  if (!digest_request->cnonce || digest_request->cnonce[0] == '\0') {
956  debugs(29, 2, "Missing cnonce field");
957  rv = authDigestLogUsername(username, digest_request, aRequestRealm);
958  safe_free(username);
959  return rv;
960  }
961 
962  /* check nc */
963  if (strlen(digest_request->nc) != 8 || strspn(digest_request->nc, "0123456789abcdefABCDEF") != 8) {
964  debugs(29, 2, "invalid nonce count");
965  rv = authDigestLogUsername(username, digest_request, aRequestRealm);
966  safe_free(username);
967  return rv;
968  }
969  } else {
970  /* RFC7616 section 3.3, qop:
971  * "MUST be used by all implementations"
972  *
973  * RFC7616 section 3.4, qop:
974  * "value MUST be one of the alternatives the server
975  * indicated it supports in the WWW-Authenticate header field"
976  *
977  * Squid sends qop=auth, reject buggy or outdated clients.
978  */
979  debugs(29, 2, "missing qop!");
980  rv = authDigestLogUsername(username, digest_request, aRequestRealm);
981  safe_free(username);
982  return rv;
983  }
984 
987  /* now the nonce */
988  nonce = authenticateDigestNonceFindNonce(digest_request->noncehex);
989  /* check that we're not being hacked / the username hasn't changed */
990  if (nonce && nonce->user && strcmp(username, nonce->user->username())) {
991  debugs(29, 2, "Username for the nonce does not equal the username for the request");
992  nonce = nullptr;
993  }
994 
995  if (!nonce) {
996  /* we couldn't find a matching nonce! */
997  debugs(29, 2, "Unexpected or invalid nonce received from " << username);
998  Auth::UserRequest::Pointer auth_request = authDigestLogUsername(username, digest_request, aRequestRealm);
999  auth_request->user()->credentials(Auth::Handshake);
1000  safe_free(username);
1001  return auth_request;
1002  }
1003 
1004  digest_request->nonce = nonce;
1005  authDigestNonceLink(nonce);
1006 
1007  /* check that we're not being hacked / the username hasn't changed */
1008  if (nonce->user && strcmp(username, nonce->user->username())) {
1009  debugs(29, 2, "Username for the nonce does not equal the username for the request");
1010  rv = authDigestLogUsername(username, digest_request, aRequestRealm);
1011  safe_free(username);
1012  return rv;
1013  }
1014 
1015  /* the method we'll check at the authenticate step as well */
1016 
1017  /* we don't send or parse opaques. Ok so we're flexible ... */
1018 
1019  /* find the user */
1020  Auth::Digest::User *digest_user;
1021 
1022  Auth::User::Pointer auth_user;
1023 
1024  SBuf key = Auth::User::BuildUserKey(username, aRequestRealm);
1025  if (key.isEmpty() || !(auth_user = Auth::Digest::User::Cache()->lookup(key))) {
1026  /* the user doesn't exist in the username cache yet */
1027  debugs(29, 9, "Creating new digest user '" << username << "'");
1028  digest_user = new Auth::Digest::User(this, aRequestRealm);
1029  /* auth_user is a parent */
1030  auth_user = digest_user;
1031  /* save the username */
1032  digest_user->username(username);
1033  /* set the user type */
1034  digest_user->auth_type = Auth::AUTH_DIGEST;
1035  /* this auth_user struct is the one to get added to the
1036  * username cache */
1037  /* store user in hash's */
1038  digest_user->addToNameCache();
1039 
1040  /*
1041  * Add the digest to the user so we can tell if a hacking
1042  * or spoofing attack is taking place. We do this by assuming
1043  * the user agent won't change user name without warning.
1044  */
1045  authDigestUserLinkNonce(digest_user, nonce);
1046 
1047  /* auth_user is now linked, we reset these values
1048  * after external auth occurs anyway */
1049  auth_user->expiretime = current_time.tv_sec;
1050  } else {
1051  debugs(29, 9, "Found user '" << username << "' in the user cache as '" << auth_user << "'");
1052  digest_user = static_cast<Auth::Digest::User *>(auth_user.getRaw());
1053  digest_user->credentials(Auth::Unchecked);
1054  xfree(username);
1055  }
1056 
1057  /*link the request and the user */
1058  assert(digest_request != nullptr);
1059 
1060  digest_request->user(digest_user);
1061  debugs(29, 9, "username = '" << digest_user->username() << "'\nrealm = '" <<
1062  digest_request->realm << "'\nqop = '" << digest_request->qop <<
1063  "'\nalgorithm = '" << digest_request->algorithm << "'\nuri = '" <<
1064  digest_request->uri << "'\nnonce = '" << digest_request->noncehex <<
1065  "'\nnc = '" << digest_request->nc << "'\ncnonce = '" <<
1066  digest_request->cnonce << "'\nresponse = '" <<
1067  digest_request->response << "'\ndigestnonce = '" << nonce << "'");
1068 
1069  return digest_request;
1070 }
1071 
Definition: parse.c:104
static AUTHSSTATS authenticateDigestStats
Definition: Config.cc:47
void parse_int(int *var)
Definition: cache_cf.cc:2540
const signed char * table
Definition: base64.h:124
void * xcalloc(size_t n, size_t sz)
Definition: xalloc.cc:71
void wordlistDestroy(wordlist **list)
destroy a wordlist
Definition: wordlist.cc:16
@ DIGEST_NONCE
Definition: Config.cc:62
const char * rawBuf() const
Definition: SquidString.h:86
SQUIDCEXTERN void SquidMD5Init(struct SquidMD5Context *context)
Definition: md5.c:73
static SBuf BuildUserKey(const char *username, const char *realm)
Definition: User.cc:229
HttpHeader header
Definition: Message.h:74
bool isEmpty() const
Definition: SBuf.h:435
Helper::ClientPointer digestauthenticators
Definition: Config.cc:49
struct node * next
Definition: parse.c:105
int authDigestNonceIsValid(digest_nonce_h *nonce, char nc[9])
Definition: Config.cc:327
@ DIGEST_ALGORITHM
Definition: Config.cc:60
static digest_nonce_h * authenticateDigestNonceFindNonce(const char *noncehex)
Definition: Config.cc:307
void storeAppendPrintf(StoreEntry *e, const char *fmt,...)
Definition: store.cc:855
HASHHASH hash_string
Definition: hash.h:45
void hash_remove_link(hash_table *, hash_link *)
Definition: hash.cc:220
@ DIGEST_NC
Definition: Config.cc:63
http_digest_attr_type
Definition: Config.cc:56
Definition: SBuf.h:93
void * alloc()
provide (and reserve) memory suitable for storing one object
Definition: Allocator.h:44
static void authenticateDigestNonceCacheCleanup(void *data)
Definition: Config.cc:237
@ DIGEST_URI
Definition: Config.cc:61
C * getRaw() const
Definition: RefCount.h:89
void CvtHex(const HASH Bin, HASHHEX Hex)
Definition: rfc2617.c:28
char * xstrncpy(char *dst, const char *src, size_t n)
Definition: xstring.cc:37
hash_link * hash_lookup(hash_table *, const void *)
Definition: hash.cc:146
int HASHCMP(const void *, const void *)
Definition: hash.h:13
static struct node * parse(FILE *fp)
Definition: parse.c:965
SQUIDCEXTERN void SquidMD5Final(uint8_t digest[16], struct SquidMD5Context *context)
static Mem::Allocator * digest_nonce_pool
Definition: Config.cc:54
virtual User::Pointer user()
Definition: UserRequest.h:143
static Pointer Make(const char *name)
Definition: helper.cc:757
void httpHeaderPutStrf(HttpHeader *hdr, Http::HdrType id, const char *fmt,...)
@ DIGEST_USERNAME
Definition: Config.cc:57
int authDigestNonceIsStale(digest_nonce_h *nonce)
Definition: Config.cc:365
static SchemeConfig * Find(const char *proxy_auth)
Definition: SchemeConfig.cc:59
@ DIGEST_INVALID_ATTR
Definition: Config.cc:66
std::mt19937::result_type RandomSeed32()
Definition: Random.cc:13
void helperShutdown(const Helper::Client::Pointer &hlp)
Definition: helper.cc:769
virtual void parse(SchemeConfig *, int, char *)
Definition: SchemeConfig.cc:84
SBuf Latin1ToUtf8(const char *in)
converts ISO-LATIN-1 to UTF-8
Definition: toUtf.cc:16
struct timeval current_time
the current UNIX time in timeval {seconds, microseconds} format
Definition: gadgets.cc:18
void parse_time_t(time_t *var)
Definition: cache_cf.cc:2952
void freeOne(void *obj)
return memory reserved by alloc()
Definition: Allocator.h:51
#define SQUIDSBUFPRINT(s)
Definition: SBuf.h:32
int httpHeaderParseQuotedString(const char *start, const int len, String *val)
void authDigestUserLinkNonce(Auth::Digest::User *user, digest_nonce_h *nonce)
Definition: Config.cc:652
struct _Cache Cache
#define xisgraph(x)
Definition: xis.h:28
#define memPoolCreate
Creates a named MemPool of elements with the given size.
Definition: Pool.h:123
@ DIGEST_CNONCE
Definition: Config.cc:64
digest_nonce_h * authenticateDigestNonceNew(void)
Definition: Config.cc:122
static int authdigest_initialised
Definition: Config.cc:53
#define safe_free(x)
Definition: xalloc.h:73
#define assert(EX)
Definition: assert.h:17
static void authenticateDigestNonceDelete(digest_nonce_h *nonce)
Definition: Config.cc:190
@ DIGEST_QOP
Definition: Config.cc:59
static Auth::UserRequest::Pointer authDigestLogUsername(char *username, Auth::UserRequest::Pointer auth_user_request, const char *requestRealm)
Definition: Config.cc:686
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
@ DIGEST_REALM
Definition: Config.cc:58
void hash_first(hash_table *)
Definition: hash.cc:172
char * xstrndup(const char *s, size_t n)
Definition: xstring.cc:56
#define xfree
void authDigestNoncePurge(digest_nonce_h *nonce)
Definition: Config.cc:428
char HASHHEX[HASHHEXLEN+1]
Definition: rfc2617.h:33
@ AUTH_DIGEST
Definition: Type.h:21
void AUTHSSTATS(StoreEntry *)
Definition: Gadgets.h:21
int authDigestNonceLastRequest(digest_nonce_h *nonce)
Definition: Config.cc:408
static const auto & digestFieldsLookupTable()
Definition: Config.cc:70
const char * termedBuf() const
Definition: SquidString.h:92
static void authDigestNonceEncode(digest_nonce_h *nonce)
Definition: Config.cc:103
@ DIGEST_RESPONSE
Definition: Config.cc:65
static void authenticateDigestNonceSetup(void)
Definition: Config.cc:203
hash_table * hash_create(HASHCMP *, int, HASHHASH *)
Definition: hash.cc:108
size_type size() const
Definition: SquidString.h:73
void RegisterAction(char const *action, char const *desc, OBJH *handler, Protected, Atomic, Format)
Definition: Registration.cc:54
void assign(const char *str, int len)
Definition: String.cc:78
#define DBG_IMPORTANT
Definition: Stream.h:38
void authDigestNonceUnlink(digest_nonce_h *nonce)
Definition: Config.cc:281
void parse_onoff(int *var)
Definition: cache_cf.cc:2580
virtual void done()
SQUIDCEXTERN void SquidMD5Update(struct SquidMD5Context *context, const void *buf, unsigned len)
Definition: md5.c:89
int shutting_down
static void authDigestNonceUserUnlink(digest_nonce_h *nonce)
Definition: Config.cc:614
virtual bool dump(StoreEntry *, const char *, SchemeConfig *) const
char HASH[HASHLEN]
Definition: rfc2617.h:31
void authenticateDigestNonceShutdown(void)
Definition: Config.cc:216
const char * authenticateDigestNonceNonceHex(const digest_nonce_h *nonce)
Definition: Config.cc:298
String SBufToString(const SBuf &s)
Definition: StringConvert.h:26
@ AUTH_BROKEN
Definition: Type.h:23
#define xisspace(x)
Definition: xis.h:15
static hash_table * digest_nonce_cache
Definition: Config.cc:51
#define IPC_STREAM
Definition: defines.h:104
hash_link * hash_next(hash_table *)
Definition: hash.cc:188
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
void hash_join(hash_table *, hash_link *)
Definition: hash.cc:131
void eventAdd(const char *name, EVH *func, void *arg, double when, int weight, bool cbdata)
Definition: event.cc:107
SBuf Cp1251ToUtf8(const char *in)
converts CP1251 to UTF-8
Definition: toUtf.cc:37
#define SQUIDSBUFPH
Definition: SBuf.h:31
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:78
static uint32 H(uint32 X, uint32 Y, uint32 Z)
Definition: md4.c:58
static void authDigestNonceLink(digest_nonce_h *nonce)
Definition: Config.cc:272

 

Introduction

Documentation

Support

Miscellaneous