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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors