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