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