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