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