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