]> git.ipfire.org Git - thirdparty/squid.git/blob - src/auth/digest/Config.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / auth / digest / Config.cc
1 /*
2 * Copyright (C) 1996-2016 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"
20 #include "auth/digest/UserRequest.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.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
46 static AUTHSSTATS authenticateDigestStats;
47
48 helper *digestauthenticators = NULL;
49
50 static hash_table *digest_nonce_cache;
51
52 static int authdigest_initialised = 0;
53 static MemAllocator *digest_nonce_pool = NULL;
54
55 enum http_digest_attr_type {
56 DIGEST_USERNAME,
57 DIGEST_REALM,
58 DIGEST_QOP,
59 DIGEST_ALGORITHM,
60 DIGEST_URI,
61 DIGEST_NONCE,
62 DIGEST_NC,
63 DIGEST_CNONCE,
64 DIGEST_RESPONSE,
65 DIGEST_INVALID_ATTR
66 };
67
68 static const LookupTable<http_digest_attr_type>::Record
69 DigestAttrs[] = {
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
82 LookupTable<http_digest_attr_type>
83 DigestFieldsLookupTable(DIGEST_INVALID_ATTR, DigestAttrs);
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 *
119 authenticateDigestNonceNew(void)
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
210 authenticateDigestNonceSetup(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 none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->nonceGCInterval, 1);
219 }
220 }
221
222 void
223 authenticateDigestNonceShutdown(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
251 authenticateDigestNonceCacheCleanup(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 */
274 authDigestNonceUserUnlink(nonce);
275 authDigestNoncePurge(nonce);
276 }
277 }
278
279 debugs(29, 3, "Finished cleaning the nonce cache.");
280
281 if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->active())
282 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, static_cast<Auth::Digest::Config*>(Auth::Config::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)
319 authenticateDigestNonceDelete(nonce);
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::Config::Find("digest"))->CheckNonceCount) {
370 /* Ignore client supplied NC */
371 intnc = nonce->nc + 1;
372 }
373
374 if ((static_cast<Auth::Digest::Config*>(Auth::Config::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::Config::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::Config::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::Config::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
428 /**
429 * \retval 0 the digest is not stale yet
430 * \retval -1 the digest will be stale on the next request
431 */
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::Config::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::Config * scheme) const
482 {
483 if (!Auth::Config::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 storeAppendPrintf(entry, "%s digest utf8 %s\n", name, utf8 ? "on" : "off");
491 return true;
492 }
493
494 bool
495 Auth::Digest::Config::active() const
496 {
497 return authdigest_initialised == 1;
498 }
499
500 bool
501 Auth::Digest::Config::configured() const
502 {
503 if ((authenticateProgram != NULL) &&
504 (authenticateChildren.n_max != 0) &&
505 !realm.isEmpty() && (noncemaxduration > -1))
506 return true;
507
508 return false;
509 }
510
511 /* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
512 void
513 Auth::Digest::Config::fixHeader(Auth::UserRequest::Pointer auth_user_request, HttpReply *rep, Http::HdrType hdrType, HttpRequest *)
514 {
515 if (!authenticateProgram)
516 return;
517
518 bool stale = false;
519 digest_nonce_h *nonce = NULL;
520
521 /* on a 407 or 401 we always use a new nonce */
522 if (auth_user_request != NULL) {
523 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
524
525 if (digest_user) {
526 stale = digest_user->credentials() == Auth::Handshake;
527 if (stale) {
528 nonce = digest_user->currentNonce();
529 }
530 }
531 }
532 if (!nonce) {
533 nonce = authenticateDigestNonceNew();
534 }
535
536 debugs(29, 9, "Sending type:" << hdrType <<
537 " header: 'Digest realm=\"" << realm << "\", nonce=\"" <<
538 authenticateDigestNonceNonceb64(nonce) << "\", qop=\"" << QOP_AUTH <<
539 "\", stale=" << (stale ? "true" : "false"));
540
541 /* in the future, for WWW auth we may want to support the domain entry */
542 httpHeaderPutStrf(&rep->header, hdrType, "Digest realm=\"" SQUIDSBUFPH "\", nonce=\"%s\", qop=\"%s\", stale=%s",
543 SQUIDSBUFPRINT(realm), authenticateDigestNonceNonceb64(nonce), QOP_AUTH, stale ? "true" : "false");
544 }
545
546 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
547 * config file */
548 void
549 Auth::Digest::Config::init(Auth::Config *)
550 {
551 if (authenticateProgram) {
552 authenticateDigestNonceSetup();
553 authdigest_initialised = 1;
554
555 if (digestauthenticators == NULL)
556 digestauthenticators = new helper("digestauthenticator");
557
558 digestauthenticators->cmdline = authenticateProgram;
559
560 digestauthenticators->childs.updateLimits(authenticateChildren);
561
562 digestauthenticators->ipc_type = IPC_STREAM;
563
564 helperOpenServers(digestauthenticators);
565 }
566 }
567
568 void
569 Auth::Digest::Config::registerWithCacheManager(void)
570 {
571 Mgr::RegisterAction("digestauthenticator",
572 "Digest User Authenticator Stats",
573 authenticateDigestStats, 0, 1);
574 }
575
576 /* free any allocated configuration details */
577 void
578 Auth::Digest::Config::done()
579 {
580 Auth::Config::done();
581
582 authdigest_initialised = 0;
583
584 if (digestauthenticators)
585 helperShutdown(digestauthenticators);
586
587 if (!shutting_down)
588 return;
589
590 delete digestauthenticators;
591 digestauthenticators = NULL;
592
593 if (authenticateProgram)
594 wordlistDestroy(&authenticateProgram);
595 }
596
597 Auth::Digest::Config::Config() :
598 nonceGCInterval(5*60),
599 noncemaxduration(30*60),
600 noncemaxuses(50),
601 NonceStrictness(0),
602 CheckNonceCount(1),
603 PostWorkaround(0),
604 utf8(0)
605 {}
606
607 void
608 Auth::Digest::Config::parse(Auth::Config * scheme, int n_configured, char *param_str)
609 {
610 if (strcmp(param_str, "program") == 0) {
611 if (authenticateProgram)
612 wordlistDestroy(&authenticateProgram);
613
614 parse_wordlist(&authenticateProgram);
615
616 requirePathnameExists("auth_param digest program", authenticateProgram->key);
617 } else if (strcmp(param_str, "nonce_garbage_interval") == 0) {
618 parse_time_t(&nonceGCInterval);
619 } else if (strcmp(param_str, "nonce_max_duration") == 0) {
620 parse_time_t(&noncemaxduration);
621 } else if (strcmp(param_str, "nonce_max_count") == 0) {
622 parse_int((int *) &noncemaxuses);
623 } else if (strcmp(param_str, "nonce_strictness") == 0) {
624 parse_onoff(&NonceStrictness);
625 } else if (strcmp(param_str, "check_nonce_count") == 0) {
626 parse_onoff(&CheckNonceCount);
627 } else if (strcmp(param_str, "post_workaround") == 0) {
628 parse_onoff(&PostWorkaround);
629 } else if (strcmp(param_str, "utf8") == 0) {
630 parse_onoff(&utf8);
631 } else
632 Auth::Config::parse(scheme, n_configured, param_str);
633 }
634
635 const char *
636 Auth::Digest::Config::type() const
637 {
638 return Auth::Digest::Scheme::GetInstance()->type();
639 }
640
641 static void
642 authenticateDigestStats(StoreEntry * sentry)
643 {
644 if (digestauthenticators)
645 digestauthenticators->packStatsInto(sentry, "Digest Authenticator Statistics");
646 }
647
648 /* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
649
650 static void
651 authDigestNonceUserUnlink(digest_nonce_h * nonce)
652 {
653 Auth::Digest::User *digest_user;
654 dlink_node *link, *tmplink;
655
656 if (!nonce)
657 return;
658
659 if (!nonce->user)
660 return;
661
662 digest_user = nonce->user;
663
664 /* unlink from the user list. Yes we're crossing structures but this is the only
665 * time this code is needed
666 */
667 link = digest_user->nonces.head;
668
669 while (link) {
670 tmplink = link;
671 link = link->next;
672
673 if (tmplink->data == nonce) {
674 dlinkDelete(tmplink, &digest_user->nonces);
675 authDigestNonceUnlink(static_cast < digest_nonce_h * >(tmplink->data));
676 delete tmplink;
677 link = NULL;
678 }
679 }
680
681 /* this reference to user was not locked because freeeing the user frees
682 * the nonce too.
683 */
684 nonce->user = NULL;
685 }
686
687 /* authDigesteserLinkNonce: add a nonce to a given user's struct */
688 void
689 authDigestUserLinkNonce(Auth::Digest::User * user, digest_nonce_h * nonce)
690 {
691 dlink_node *node;
692
693 if (!user || !nonce || !nonce->user)
694 return;
695
696 Auth::Digest::User *digest_user = user;
697
698 node = digest_user->nonces.head;
699
700 while (node && (node->data != nonce))
701 node = node->next;
702
703 if (node)
704 return;
705
706 node = new dlink_node;
707
708 dlinkAddTail(nonce, node, &digest_user->nonces);
709
710 authDigestNonceLink(nonce);
711
712 /* ping this nonce to this auth user */
713 assert((nonce->user == NULL) || (nonce->user == user));
714
715 /* we don't lock this reference because removing the user removes the
716 * hash too. Of course if that changes we're stuffed so read the code huh?
717 */
718 nonce->user = user;
719 }
720
721 /* setup the necessary info to log the username */
722 static Auth::UserRequest::Pointer
723 authDigestLogUsername(char *username, Auth::UserRequest::Pointer auth_user_request, const char *requestRealm)
724 {
725 assert(auth_user_request != NULL);
726
727 /* log the username */
728 debugs(29, 9, "Creating new user for logging '" << (username?username:"[no username]") << "'");
729 Auth::User::Pointer digest_user = new Auth::Digest::User(static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest")), requestRealm);
730 /* save the credentials */
731 digest_user->username(username);
732 /* set the auth_user type */
733 digest_user->auth_type = Auth::AUTH_BROKEN;
734 /* link the request to the user */
735 auth_user_request->user(digest_user);
736 return auth_user_request;
737 }
738
739 /*
740 * Decode a Digest [Proxy-]Auth string, placing the results in the passed
741 * Auth_user structure.
742 */
743 Auth::UserRequest::Pointer
744 Auth::Digest::Config::decode(char const *proxy_auth, const char *aRequestRealm)
745 {
746 const char *item;
747 const char *p;
748 const char *pos = NULL;
749 char *username = NULL;
750 digest_nonce_h *nonce;
751 int ilen;
752
753 debugs(29, 9, "beginning");
754
755 Auth::Digest::UserRequest *digest_request = new Auth::Digest::UserRequest();
756
757 /* trim DIGEST from string */
758
759 while (xisgraph(*proxy_auth))
760 ++proxy_auth;
761
762 /* Trim leading whitespace before decoding */
763 while (xisspace(*proxy_auth))
764 ++proxy_auth;
765
766 String temp(proxy_auth);
767
768 while (strListGetItem(&temp, ',', &item, &ilen, &pos)) {
769 /* isolate directive name & value */
770 size_t nlen;
771 size_t vlen;
772 if ((p = (const char *)memchr(item, '=', ilen)) && (p - item < ilen)) {
773 nlen = p - item;
774 ++p;
775 vlen = ilen - (p - item);
776 } else {
777 nlen = ilen;
778 vlen = 0;
779 }
780
781 SBuf keyName(item, nlen);
782 String value;
783
784 if (vlen > 0) {
785 // see RFC 2617 section 3.2.1 and 3.2.2 for details on the BNF
786
787 if (keyName == SBuf("domain",6) || keyName == SBuf("uri",3)) {
788 // domain is Special. Not a quoted-string, must not be de-quoted. But is wrapped in '"'
789 // BUG 3077: uri= can also be sent to us in a mangled (invalid!) form like domain
790 if (*p == '"' && *(p + vlen -1) == '"') {
791 value.limitInit(p+1, vlen-2);
792 }
793 } else if (keyName == SBuf("qop",3)) {
794 // qop is more special.
795 // On request this must not be quoted-string de-quoted. But is several values wrapped in '"'
796 // On response this is a single un-quoted token.
797 if (*p == '"' && *(p + vlen -1) == '"') {
798 value.limitInit(p+1, vlen-2);
799 } else {
800 value.limitInit(p, vlen);
801 }
802 } else if (*p == '"') {
803 if (!httpHeaderParseQuotedString(p, vlen, &value)) {
804 debugs(29, 9, "Failed to parse attribute '" << item << "' in '" << temp << "'");
805 continue;
806 }
807 } else {
808 value.limitInit(p, vlen);
809 }
810 } else {
811 debugs(29, 9, "Failed to parse attribute '" << item << "' in '" << temp << "'");
812 continue;
813 }
814
815 /* find type */
816 const http_digest_attr_type t = DigestFieldsLookupTable.lookup(keyName);
817
818 switch (t) {
819 case DIGEST_USERNAME:
820 safe_free(username);
821 if (value.size() != 0)
822 username = xstrndup(value.rawBuf(), value.size() + 1);
823 debugs(29, 9, "Found Username '" << username << "'");
824 break;
825
826 case DIGEST_REALM:
827 safe_free(digest_request->realm);
828 if (value.size() != 0)
829 digest_request->realm = xstrndup(value.rawBuf(), value.size() + 1);
830 debugs(29, 9, "Found realm '" << digest_request->realm << "'");
831 break;
832
833 case DIGEST_QOP:
834 safe_free(digest_request->qop);
835 if (value.size() != 0)
836 digest_request->qop = xstrndup(value.rawBuf(), value.size() + 1);
837 debugs(29, 9, "Found qop '" << digest_request->qop << "'");
838 break;
839
840 case DIGEST_ALGORITHM:
841 safe_free(digest_request->algorithm);
842 if (value.size() != 0)
843 digest_request->algorithm = xstrndup(value.rawBuf(), value.size() + 1);
844 debugs(29, 9, "Found algorithm '" << digest_request->algorithm << "'");
845 break;
846
847 case DIGEST_URI:
848 safe_free(digest_request->uri);
849 if (value.size() != 0)
850 digest_request->uri = xstrndup(value.rawBuf(), value.size() + 1);
851 debugs(29, 9, "Found uri '" << digest_request->uri << "'");
852 break;
853
854 case DIGEST_NONCE:
855 safe_free(digest_request->nonceb64);
856 if (value.size() != 0)
857 digest_request->nonceb64 = xstrndup(value.rawBuf(), value.size() + 1);
858 debugs(29, 9, "Found nonce '" << digest_request->nonceb64 << "'");
859 break;
860
861 case DIGEST_NC:
862 if (value.size() != 8) {
863 debugs(29, 9, "Invalid nc '" << value << "' in '" << temp << "'");
864 }
865 xstrncpy(digest_request->nc, value.rawBuf(), value.size() + 1);
866 debugs(29, 9, "Found noncecount '" << digest_request->nc << "'");
867 break;
868
869 case DIGEST_CNONCE:
870 safe_free(digest_request->cnonce);
871 if (value.size() != 0)
872 digest_request->cnonce = xstrndup(value.rawBuf(), value.size() + 1);
873 debugs(29, 9, "Found cnonce '" << digest_request->cnonce << "'");
874 break;
875
876 case DIGEST_RESPONSE:
877 safe_free(digest_request->response);
878 if (value.size() != 0)
879 digest_request->response = xstrndup(value.rawBuf(), value.size() + 1);
880 debugs(29, 9, "Found response '" << digest_request->response << "'");
881 break;
882
883 default:
884 debugs(29, 3, "Unknown attribute '" << item << "' in '" << temp << "'");
885 break;
886 }
887 }
888
889 temp.clean();
890
891 /* now we validate the data given to us */
892
893 /*
894 * TODO: on invalid parameters we should return 400, not 407.
895 * Find some clean way of doing this. perhaps return a valid
896 * struct, and set the direction to clientwards combined with
897 * a change to the clientwards handling code (ie let the
898 * clientwards call set the error type (but limited to known
899 * correct values - 400/401/407
900 */
901
902 /* 2069 requirements */
903
904 // return value.
905 Auth::UserRequest::Pointer rv;
906 /* do we have a username ? */
907 if (!username || username[0] == '\0') {
908 debugs(29, 2, "Empty or not present username");
909 rv = authDigestLogUsername(username, digest_request, aRequestRealm);
910 safe_free(username);
911 return rv;
912 }
913
914 /* Sanity check of the username.
915 * " can not be allowed in usernames until * the digest helper protocol
916 * have been redone
917 */
918 if (strchr(username, '"')) {
919 debugs(29, 2, "Unacceptable username '" << username << "'");
920 rv = authDigestLogUsername(username, digest_request, aRequestRealm);
921 safe_free(username);
922 return rv;
923 }
924
925 /* do we have a realm ? */
926 if (!digest_request->realm || digest_request->realm[0] == '\0') {
927 debugs(29, 2, "Empty or not present realm");
928 rv = authDigestLogUsername(username, digest_request, aRequestRealm);
929 safe_free(username);
930 return rv;
931 }
932
933 /* and a nonce? */
934 if (!digest_request->nonceb64 || digest_request->nonceb64[0] == '\0') {
935 debugs(29, 2, "Empty or not present nonce");
936 rv = authDigestLogUsername(username, digest_request, aRequestRealm);
937 safe_free(username);
938 return rv;
939 }
940
941 /* we can't check the URI just yet. We'll check it in the
942 * authenticate phase, but needs to be given */
943 if (!digest_request->uri || digest_request->uri[0] == '\0') {
944 debugs(29, 2, "Missing URI field");
945 rv = authDigestLogUsername(username, digest_request, aRequestRealm);
946 safe_free(username);
947 return rv;
948 }
949
950 /* is the response the correct length? */
951 if (!digest_request->response || strlen(digest_request->response) != 32) {
952 debugs(29, 2, "Response length invalid");
953 rv = authDigestLogUsername(username, digest_request, aRequestRealm);
954 safe_free(username);
955 return rv;
956 }
957
958 /* check the algorithm is present and supported */
959 if (!digest_request->algorithm)
960 digest_request->algorithm = xstrndup("MD5", 4);
961 else if (strcmp(digest_request->algorithm, "MD5")
962 && strcmp(digest_request->algorithm, "MD5-sess")) {
963 debugs(29, 2, "invalid algorithm specified!");
964 rv = authDigestLogUsername(username, digest_request, aRequestRealm);
965 safe_free(username);
966 return rv;
967 }
968
969 /* 2617 requirements, indicated by qop */
970 if (digest_request->qop) {
971
972 /* check the qop is what we expected. */
973 if (strcmp(digest_request->qop, QOP_AUTH) != 0) {
974 /* we received a qop option we didn't send */
975 debugs(29, 2, "Invalid qop option received");
976 rv = authDigestLogUsername(username, digest_request, aRequestRealm);
977 safe_free(username);
978 return rv;
979 }
980
981 /* check cnonce */
982 if (!digest_request->cnonce || digest_request->cnonce[0] == '\0') {
983 debugs(29, 2, "Missing cnonce field");
984 rv = authDigestLogUsername(username, digest_request, aRequestRealm);
985 safe_free(username);
986 return rv;
987 }
988
989 /* check nc */
990 if (strlen(digest_request->nc) != 8 || strspn(digest_request->nc, "0123456789abcdefABCDEF") != 8) {
991 debugs(29, 2, "invalid nonce count");
992 rv = authDigestLogUsername(username, digest_request, aRequestRealm);
993 safe_free(username);
994 return rv;
995 }
996 } else {
997 /* cnonce and nc both require qop */
998 if (digest_request->cnonce || digest_request->nc[0] != '\0') {
999 debugs(29, 2, "missing qop!");
1000 rv = authDigestLogUsername(username, digest_request, aRequestRealm);
1001 safe_free(username);
1002 return rv;
1003 }
1004 }
1005
1006 /** below nonce state dependent **/
1007
1008 /* now the nonce */
1009 nonce = authenticateDigestNonceFindNonce(digest_request->nonceb64);
1010 /* check that we're not being hacked / the username hasn't changed */
1011 if (nonce && nonce->user && strcmp(username, nonce->user->username())) {
1012 debugs(29, 2, "Username for the nonce does not equal the username for the request");
1013 nonce = NULL;
1014 }
1015
1016 if (!nonce) {
1017 /* we couldn't find a matching nonce! */
1018 debugs(29, 2, "Unexpected or invalid nonce received from " << username);
1019 Auth::UserRequest::Pointer auth_request = authDigestLogUsername(username, digest_request, aRequestRealm);
1020 auth_request->user()->credentials(Auth::Handshake);
1021 safe_free(username);
1022 return auth_request;
1023 }
1024
1025 digest_request->nonce = nonce;
1026 authDigestNonceLink(nonce);
1027
1028 /* check that we're not being hacked / the username hasn't changed */
1029 if (nonce->user && strcmp(username, nonce->user->username())) {
1030 debugs(29, 2, "Username for the nonce does not equal the username for the request");
1031 rv = authDigestLogUsername(username, digest_request, aRequestRealm);
1032 safe_free(username);
1033 return rv;
1034 }
1035
1036 /* the method we'll check at the authenticate step as well */
1037
1038 /* we don't send or parse opaques. Ok so we're flexable ... */
1039
1040 /* find the user */
1041 Auth::Digest::User *digest_user;
1042
1043 Auth::User::Pointer auth_user;
1044
1045 SBuf key = Auth::User::BuildUserKey(username, aRequestRealm);
1046 if (key.isEmpty() || !(auth_user = Auth::Digest::User::Cache()->lookup(key))) {
1047 /* the user doesn't exist in the username cache yet */
1048 debugs(29, 9, "Creating new digest user '" << username << "'");
1049 digest_user = new Auth::Digest::User(this, aRequestRealm);
1050 /* auth_user is a parent */
1051 auth_user = digest_user;
1052 /* save the username */
1053 digest_user->username(username);
1054 /* set the user type */
1055 digest_user->auth_type = Auth::AUTH_DIGEST;
1056 /* this auth_user struct is the one to get added to the
1057 * username cache */
1058 /* store user in hash's */
1059 digest_user->addToNameCache();
1060
1061 /*
1062 * Add the digest to the user so we can tell if a hacking
1063 * or spoofing attack is taking place. We do this by assuming
1064 * the user agent won't change user name without warning.
1065 */
1066 authDigestUserLinkNonce(digest_user, nonce);
1067 } else {
1068 debugs(29, 9, "Found user '" << username << "' in the user cache as '" << auth_user << "'");
1069 digest_user = static_cast<Auth::Digest::User *>(auth_user.getRaw());
1070 digest_user->credentials(Auth::Unchecked);
1071 xfree(username);
1072 }
1073
1074 /*link the request and the user */
1075 assert(digest_request != NULL);
1076
1077 digest_request->user(digest_user);
1078 debugs(29, 9, "username = '" << digest_user->username() << "'\nrealm = '" <<
1079 digest_request->realm << "'\nqop = '" << digest_request->qop <<
1080 "'\nalgorithm = '" << digest_request->algorithm << "'\nuri = '" <<
1081 digest_request->uri << "'\nnonce = '" << digest_request->nonceb64 <<
1082 "'\nnc = '" << digest_request->nc << "'\ncnonce = '" <<
1083 digest_request->cnonce << "'\nresponse = '" <<
1084 digest_request->response << "'\ndigestnonce = '" << nonce << "'");
1085
1086 return digest_request;
1087 }
1088