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