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