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