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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors