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