]> git.ipfire.org Git - thirdparty/squid.git/blame - src/auth/digest/auth_digest.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / auth / digest / auth_digest.cc
CommitLineData
2d70df72 1/*
262a0e14 2 * $Id$
2d70df72 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.
26ac0430 23 *
2d70df72 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.
26ac0430 28 *
2d70df72 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.h"
3ad63615 43#include "auth/Gadgets.h"
a553a5a3 44#include "event.h"
62ee09ca 45#include "CacheManager.h"
e6ccf245 46#include "Store.h"
a2ac85d9 47#include "HttpRequest.h"
924f73bc 48#include "HttpReply.h"
d295d770 49#include "wordlist.h"
cc192b50 50#include "SquidTime.h"
f5691f9c 51/* TODO don't include this */
52#include "digestScheme.h"
c78aa667 53
2d70df72 54/* Digest Scheme */
55
56static HLPCB authenticateDigestHandleReply;
2d70df72 57static AUTHSSTATS authenticateDigestStats;
2d70df72 58
59static helper *digestauthenticators = NULL;
60
61static hash_table *digest_nonce_cache;
62
f5691f9c 63static AuthDigestConfig digestConfig;
2d70df72 64
65static int authdigest_initialised = 0;
a3efa961 66static MemAllocator *digest_nonce_pool = NULL;
2d70df72 67
e6ccf245 68CBDATA_TYPE(DigestAuthenticateStateData);
2d70df72 69
9abd1514 70enum http_digest_attr_type {
d6b7a3c4 71 DIGEST_USERNAME,
cb14509d
HN
72 DIGEST_REALM,
73 DIGEST_QOP,
74 DIGEST_ALGORITHM,
75 DIGEST_URI,
76 DIGEST_NONCE,
77 DIGEST_NC,
78 DIGEST_CNONCE,
79 DIGEST_RESPONSE,
80 DIGEST_ENUM_END
81};
82
83static const HttpHeaderFieldAttrs DigestAttrs[DIGEST_ENUM_END] = {
84 {"username", (http_hdr_type)DIGEST_USERNAME},
85 {"realm", (http_hdr_type)DIGEST_REALM},
86 {"qop", (http_hdr_type)DIGEST_QOP},
87 {"algorithm", (http_hdr_type)DIGEST_ALGORITHM},
88 {"uri", (http_hdr_type)DIGEST_URI},
89 {"nonce", (http_hdr_type)DIGEST_NONCE},
90 {"nc", (http_hdr_type)DIGEST_NC},
91 {"cnonce", (http_hdr_type)DIGEST_CNONCE},
92 {"response", (http_hdr_type)DIGEST_RESPONSE},
93};
94
95static HttpHeaderFieldInfo *DigestFieldsInfo = NULL;
96
2d70df72 97/*
98 *
99 * Nonce Functions
100 *
101 */
102
103static void authenticateDigestNonceCacheCleanup(void *data);
104static digest_nonce_h *authenticateDigestNonceFindNonce(const char *nonceb64);
c193c972 105static digest_nonce_h *authenticateDigestNonceNew(void);
c78aa667 106static void authenticateDigestNonceDelete(digest_nonce_h * nonce);
c193c972 107static void authenticateDigestNonceSetup(void);
108static void authenticateDigestNonceShutdown(void);
109static void authenticateDigestNonceReconfigure(void);
c78aa667 110static const char *authenticateDigestNonceNonceb64(digest_nonce_h * nonce);
111static int authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9]);
112static int authDigestNonceIsStale(digest_nonce_h * nonce);
113static void authDigestNonceEncode(digest_nonce_h * nonce);
114static int authDigestNonceLastRequest(digest_nonce_h * nonce);
115static void authDigestNonceLink(digest_nonce_h * nonce);
116static void authDigestNonceUnlink(digest_nonce_h * nonce);
117#if NOT_USED
118static int authDigestNonceLinks(digest_nonce_h * nonce);
119#endif
120static void authDigestNonceUserUnlink(digest_nonce_h * nonce);
121static void authDigestNoncePurge(digest_nonce_h * nonce);
2d70df72 122
c78aa667 123static void
2d70df72 124authDigestNonceEncode(digest_nonce_h * nonce)
125{
126 if (!nonce)
62e76326 127 return;
128
4a8b20e8 129 if (nonce->key)
62e76326 130 xfree(nonce->key);
131
4a8b20e8 132 nonce->key = xstrdup(base64_encode_bin((char *) &(nonce->noncedata), sizeof(digest_nonce_data)));
2d70df72 133}
134
c78aa667 135static digest_nonce_h *
c193c972 136authenticateDigestNonceNew(void)
2d70df72 137{
b001e822 138 digest_nonce_h *newnonce = static_cast < digest_nonce_h * >(digest_nonce_pool->alloc());
2d70df72 139 digest_nonce_h *temp;
140
62e76326 141 /* NONCE CREATION - NOTES AND REASONING. RBC 20010108
142 * === EXCERPT FROM RFC 2617 ===
143 * The contents of the nonce are implementation dependent. The quality
144 * of the implementation depends on a good choice. A nonce might, for
145 * example, be constructed as the base 64 encoding of
26ac0430 146 *
62e76326 147 * time-stamp H(time-stamp ":" ETag ":" private-key)
26ac0430 148 *
62e76326 149 * where time-stamp is a server-generated time or other non-repeating
150 * value, ETag is the value of the HTTP ETag header associated with
151 * the requested entity, and private-key is data known only to the
152 * server. With a nonce of this form a server would recalculate the
153 * hash portion after receiving the client authentication header and
154 * reject the request if it did not match the nonce from that header
155 * or if the time-stamp value is not recent enough. In this way the
156 * server can limit the time of the nonce's validity. The inclusion of
157 * the ETag prevents a replay request for an updated version of the
158 * resource. (Note: including the IP address of the client in the
159 * nonce would appear to offer the server the ability to limit the
160 * reuse of the nonce to the same client that originally got it.
161 * However, that would break proxy farms, where requests from a single
162 * user often go through different proxies in the farm. Also, IP
163 * address spoofing is not that hard.)
164 * ====
26ac0430 165 *
62e76326 166 * Now for my reasoning:
167 * We will not accept a unrecognised nonce->we have all recognisable
82b045dc 168 * nonces stored. If we send out unique base64 encodings we guarantee
62e76326 169 * that a given nonce applies to only one user (barring attacks or
170 * really bad timing with expiry and creation). Using a random
171 * component in the nonce allows us to loop to find a unique nonce.
172 * We use H(nonce_data) so the nonce is meaningless to the reciever.
173 * So our nonce looks like base64(H(timestamp,pointertohash,randomdata))
174 * And even if our randomness is not very random (probably due to
175 * bad coding on my part) we don't really care - the timestamp and
82b045dc 176 * memory pointer also guarantee local uniqueness in the input to the hash
177 * function.
62e76326 178 */
2d70df72 179
180 /* create a new nonce */
181 newnonce->nc = 0;
182 newnonce->flags.valid = 1;
183 newnonce->noncedata.self = newnonce;
184 newnonce->noncedata.creationtime = current_time.tv_sec;
b600a19d 185 newnonce->noncedata.randomdata = squid_random();
2d70df72 186
187 authDigestNonceEncode(newnonce);
74830fc8 188 /*
189 * loop until we get a unique nonce. The nonce creation must
190 * have a random factor
191 */
62e76326 192
2f44bd34 193 while ((temp = authenticateDigestNonceFindNonce((char const *) (newnonce->key)))) {
62e76326 194 /* create a new nonce */
195 newnonce->noncedata.randomdata = squid_random();
196 authDigestNonceEncode(newnonce);
2d70df72 197 }
62e76326 198
4a8b20e8 199 hash_join(digest_nonce_cache, newnonce);
2d70df72 200 /* the cache's link */
201 authDigestNonceLink(newnonce);
202 newnonce->flags.incache = 1;
4a7a3d56 203 debugs(29, 5, "authenticateDigestNonceNew: created nonce " << newnonce << " at " << newnonce->noncedata.creationtime);
2d70df72 204 return newnonce;
205}
206
c78aa667 207static void
2d70df72 208authenticateDigestNonceDelete(digest_nonce_h * nonce)
209{
210 if (nonce) {
62e76326 211 assert(nonce->references == 0);
2d70df72 212#if UNREACHABLECODE
62e76326 213
214 if (nonce->flags.incache)
215 hash_remove_link(digest_nonce_cache, nonce);
216
2d70df72 217#endif
62e76326 218
219 assert(nonce->flags.incache == 0);
220
221 safe_free(nonce->key);
222
b001e822 223 digest_nonce_pool->free(nonce);
2d70df72 224 }
225}
226
c78aa667 227static void
c193c972 228authenticateDigestNonceSetup(void)
2d70df72 229{
230 if (!digest_nonce_pool)
04eb0689 231 digest_nonce_pool = memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h));
62e76326 232
2d70df72 233 if (!digest_nonce_cache) {
30abd221 234 digest_nonce_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string);
62e76326 235 assert(digest_nonce_cache);
f5691f9c 236 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, digestConfig.nonceGCInterval, 1);
2d70df72 237 }
238}
239
c78aa667 240static void
c193c972 241authenticateDigestNonceShutdown(void)
2d70df72 242{
62e76326 243 /*
2d70df72 244 * We empty the cache of any nonces left in there.
245 */
246 digest_nonce_h *nonce;
62e76326 247
2d70df72 248 if (digest_nonce_cache) {
bf8fe701 249 debugs(29, 2, "authenticateDigestNonceShutdown: Shutting down nonce cache ");
62e76326 250 hash_first(digest_nonce_cache);
251
252 while ((nonce = ((digest_nonce_h *) hash_next(digest_nonce_cache)))) {
253 assert(nonce->flags.incache);
254 authDigestNoncePurge(nonce);
255 }
2d70df72 256 }
62e76326 257
2f44bd34 258#if DEBUGSHUTDOWN
2d70df72 259 if (digest_nonce_pool) {
b001e822 260 delete digest_nonce_pool;
261 digest_nonce_pool = NULL;
2d70df72 262 }
62e76326 263
2f44bd34 264#endif
bf8fe701 265 debugs(29, 2, "authenticateDigestNonceShutdown: Nonce cache shutdown");
2d70df72 266}
267
c78aa667 268static void
c193c972 269authenticateDigestNonceReconfigure(void)
62e76326 270{}
2d70df72 271
c78aa667 272static void
2d70df72 273authenticateDigestNonceCacheCleanup(void *data)
274{
275 /*
74830fc8 276 * We walk the hash by nonceb64 as that is the unique key we
277 * use. For big hash tables we could consider stepping through
278 * the cache, 100/200 entries at a time. Lets see how it flies
279 * first.
2d70df72 280 */
281 digest_nonce_h *nonce;
bf8fe701 282 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Cleaning the nonce cache now");
4a7a3d56 283 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Current time: " << current_time.tv_sec);
2d70df72 284 hash_first(digest_nonce_cache);
62e76326 285
2d70df72 286 while ((nonce = ((digest_nonce_h *) hash_next(digest_nonce_cache)))) {
bf8fe701 287 debugs(29, 3, "authenticateDigestNonceCacheCleanup: nonce entry : " << nonce << " '" << (char *) nonce->key << "'");
4a7a3d56 288 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Creation time: " << nonce->noncedata.creationtime);
62e76326 289
290 if (authDigestNonceIsStale(nonce)) {
bf8fe701 291 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Removing nonce " << (char *) nonce->key << " from cache due to timeout.");
62e76326 292 assert(nonce->flags.incache);
293 /* invalidate nonce so future requests fail */
294 nonce->flags.valid = 0;
295 /* if it is tied to a auth_user, remove the tie */
296 authDigestNonceUserUnlink(nonce);
297 authDigestNoncePurge(nonce);
298 }
2d70df72 299 }
62e76326 300
bf8fe701 301 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Finished cleaning the nonce cache.");
62e76326 302
f5691f9c 303 if (digestConfig.active())
304 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, digestConfig.nonceGCInterval, 1);
2d70df72 305}
306
c78aa667 307static void
2d70df72 308authDigestNonceLink(digest_nonce_h * nonce)
309{
310 assert(nonce != NULL);
311 nonce->references++;
bf8fe701 312 debugs(29, 9, "authDigestNonceLink: nonce '" << nonce << "' now at '" << nonce->references << "'.");
2d70df72 313}
314
c78aa667 315#if NOT_USED
316static int
2d70df72 317authDigestNonceLinks(digest_nonce_h * nonce)
318{
319 if (!nonce)
62e76326 320 return -1;
321
2d70df72 322 return nonce->references;
323}
62e76326 324
c78aa667 325#endif
2d70df72 326
c78aa667 327static void
2d70df72 328authDigestNonceUnlink(digest_nonce_h * nonce)
329{
330 assert(nonce != NULL);
62e76326 331
2d70df72 332 if (nonce->references > 0) {
62e76326 333 nonce->references--;
2d70df72 334 } else {
bf8fe701 335 debugs(29, 1, "authDigestNonceUnlink; Attempt to lower nonce " << nonce << " refcount below 0!");
2d70df72 336 }
62e76326 337
bf8fe701 338 debugs(29, 9, "authDigestNonceUnlink: nonce '" << nonce << "' now at '" << nonce->references << "'.");
62e76326 339
2d70df72 340 if (nonce->references == 0)
62e76326 341 authenticateDigestNonceDelete(nonce);
2d70df72 342}
343
c78aa667 344static const char *
2d70df72 345authenticateDigestNonceNonceb64(digest_nonce_h * nonce)
346{
347 if (!nonce)
62e76326 348 return NULL;
349
2f44bd34 350 return (char const *) nonce->key;
2d70df72 351}
352
c78aa667 353static digest_nonce_h *
2d70df72 354authenticateDigestNonceFindNonce(const char *nonceb64)
355{
356 digest_nonce_h *nonce = NULL;
62e76326 357
2d70df72 358 if (nonceb64 == NULL)
62e76326 359 return NULL;
360
26ac0430 361 debugs(29, 9, "authDigestNonceFindNonce:looking for nonceb64 '" << nonceb64 << "' in the nonce cache.");
62e76326 362
2f44bd34 363 nonce = static_cast < digest_nonce_h * >(hash_lookup(digest_nonce_cache, nonceb64));
62e76326 364
e6ccf245 365 if ((nonce == NULL) || (strcmp(authenticateDigestNonceNonceb64(nonce), nonceb64)))
62e76326 366 return NULL;
367
bf8fe701 368 debugs(29, 9, "authDigestNonceFindNonce: Found nonce '" << nonce << "'");
62e76326 369
2d70df72 370 return nonce;
371}
372
c78aa667 373static int
2d70df72 374authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9])
375{
d205783b 376 unsigned long intnc;
2d70df72 377 /* do we have a nonce ? */
62e76326 378
2d70df72 379 if (!nonce)
62e76326 380 return 0;
381
d205783b 382 intnc = strtol(nc, NULL, 16);
62e76326 383
f5292c64 384 /* has it already been invalidated ? */
385 if (!nonce->flags.valid) {
bf8fe701 386 debugs(29, 4, "authDigestNonceIsValid: Nonce already invalidated");
f5292c64 387 return 0;
388 }
389
390 /* is the nonce-count ok ? */
f5691f9c 391 if (!digestConfig.CheckNonceCount) {
f5292c64 392 nonce->nc++;
393 return -1; /* forced OK by configuration */
394 }
395
f5691f9c 396 if ((digestConfig.NonceStrictness && intnc != nonce->nc + 1) ||
62e76326 397 intnc < nonce->nc + 1) {
bf8fe701 398 debugs(29, 4, "authDigestNonceIsValid: Nonce count doesn't match");
62e76326 399 nonce->flags.valid = 0;
400 return 0;
2d70df72 401 }
62e76326 402
2d70df72 403 /* seems ok */
d205783b 404 /* increment the nonce count - we've already checked that intnc is a
405 * valid representation for us, so we don't need the test here.
406 */
407 nonce->nc = intnc;
62e76326 408
2d70df72 409 return -1;
410}
411
c78aa667 412static int
2d70df72 413authDigestNonceIsStale(digest_nonce_h * nonce)
414{
415 /* do we have a nonce ? */
62e76326 416
2d70df72 417 if (!nonce)
62e76326 418 return -1;
419
2d70df72 420 /* has it's max duration expired? */
f5691f9c 421 if (nonce->noncedata.creationtime + digestConfig.noncemaxduration < current_time.tv_sec) {
bf8fe701 422 debugs(29, 4, "authDigestNonceIsStale: Nonce is too old. " <<
4a7a3d56 423 nonce->noncedata.creationtime << " " <<
424 digestConfig.noncemaxduration << " " <<
425 current_time.tv_sec);
bf8fe701 426
62e76326 427 nonce->flags.valid = 0;
428 return -1;
2d70df72 429 }
62e76326 430
2d70df72 431 if (nonce->nc > 99999998) {
bf8fe701 432 debugs(29, 4, "authDigestNonceIsStale: Nonce count overflow");
62e76326 433 nonce->flags.valid = 0;
434 return -1;
2d70df72 435 }
62e76326 436
f5691f9c 437 if (nonce->nc > digestConfig.noncemaxuses) {
bf8fe701 438 debugs(29, 4, "authDigestNoncelastRequest: Nonce count over user limit");
62e76326 439 nonce->flags.valid = 0;
440 return -1;
2d70df72 441 }
62e76326 442
2d70df72 443 /* seems ok */
444 return 0;
445}
446
62e76326 447/* return -1 if the digest will be stale on the next request */
c78aa667 448static int
2d70df72 449authDigestNonceLastRequest(digest_nonce_h * nonce)
450{
451 if (!nonce)
62e76326 452 return -1;
453
2d70df72 454 if (nonce->nc == 99999997) {
bf8fe701 455 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to overflow");
62e76326 456 return -1;
2d70df72 457 }
62e76326 458
f5691f9c 459 if (nonce->nc >= digestConfig.noncemaxuses - 1) {
bf8fe701 460 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to hit user limit");
62e76326 461 return -1;
2d70df72 462 }
62e76326 463
2d70df72 464 /* and other tests are possible. */
465 return 0;
466}
467
c78aa667 468static void
2d70df72 469authDigestNoncePurge(digest_nonce_h * nonce)
470{
471 if (!nonce)
62e76326 472 return;
473
2d70df72 474 if (!nonce->flags.incache)
62e76326 475 return;
476
4a8b20e8 477 hash_remove_link(digest_nonce_cache, nonce);
62e76326 478
2d70df72 479 nonce->flags.incache = 0;
62e76326 480
2d70df72 481 /* the cache's link */
482 authDigestNonceUnlink(nonce);
483}
484
62e76326 485/* USER related functions */
e1f7507e 486static AuthUser *
2d70df72 487authDigestUserFindUsername(const char *username)
488{
e1f7507e
AJ
489 AuthUserHashPointer *usernamehash;
490 AuthUser *auth_user;
491 debugs(29, 9, HERE << "Looking for user '" << username << "'");
62e76326 492
2f44bd34 493 if (username && (usernamehash = static_cast < auth_user_hash_pointer * >(hash_lookup(proxy_auth_username_cache, username)))) {
f5691f9c 494 while ((usernamehash->user()->auth_type != AUTH_DIGEST) &&
62e76326 495 (usernamehash->next))
496 usernamehash = static_cast < auth_user_hash_pointer * >(usernamehash->next);
497
498 auth_user = NULL;
499
f5691f9c 500 if (usernamehash->user()->auth_type == AUTH_DIGEST) {
501 auth_user = usernamehash->user();
62e76326 502 }
503
504 return auth_user;
2d70df72 505 }
62e76326 506
2d70df72 507 return NULL;
508}
509
c78aa667 510static void
c193c972 511authDigestUserShutdown(void)
2d70df72 512{
e1f7507e
AJ
513 /** \todo Future work: the auth framework could flush it's cache */
514 AuthUserHashPointer *usernamehash;
515 AuthUser *auth_user;
2d70df72 516 hash_first(proxy_auth_username_cache);
62e76326 517
2d70df72 518 while ((usernamehash = ((auth_user_hash_pointer *) hash_next(proxy_auth_username_cache)))) {
f5691f9c 519 auth_user = usernamehash->user();
62e76326 520
f5691f9c 521 if (strcmp(auth_user->config->type(), "digest") == 0)
522 auth_user->unlock();
2d70df72 523 }
2d70df72 524}
525
e1f7507e 526/** delete the digest request structure. Does NOT delete related structures */
f5691f9c 527void
528digestScheme::done()
2d70df72 529{
e1f7507e 530 /** \todo this should be a Config call. */
2d70df72 531
2d70df72 532 if (digestauthenticators)
62e76326 533 helperShutdown(digestauthenticators);
534
cb14509d
HN
535 httpHeaderDestroyFieldsInfo(DigestFieldsInfo, DIGEST_ENUM_END);
536 DigestFieldsInfo = NULL;
537
2d70df72 538 authdigest_initialised = 0;
62e76326 539
2d70df72 540 if (!shutting_down) {
62e76326 541 authenticateDigestNonceReconfigure();
542 return;
2d70df72 543 }
62e76326 544
48d54e4d
AJ
545 delete digestauthenticators;
546 digestauthenticators = NULL;
62e76326 547
2d70df72 548 authDigestUserShutdown();
549 authenticateDigestNonceShutdown();
bf8fe701 550 debugs(29, 2, "authenticateDigestDone: Digest authentication shut down.");
2d70df72 551}
552
f5691f9c 553void
554AuthDigestConfig::dump(StoreEntry * entry, const char *name, AuthConfig * scheme)
2d70df72 555{
f5691f9c 556 wordlist *list = authenticate;
bf8fe701 557 debugs(29, 9, "authDigestCfgDump: Dumping configuration");
2d70df72 558 storeAppendPrintf(entry, "%s %s", name, "digest");
62e76326 559
2d70df72 560 while (list != NULL) {
62e76326 561 storeAppendPrintf(entry, " %s", list->key);
562 list = list->next;
2d70df72 563 }
62e76326 564
404cfda1 565 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",
f5691f9c 566 name, "digest", digestAuthRealm,
404cfda1 567 name, "digest", authenticateChildren.n_max, authenticateChildren.n_startup, authenticateChildren.n_idle, authenticateChildren.concurrency,
f5691f9c 568 name, "digest", noncemaxuses,
569 name, "digest", (int) noncemaxduration,
570 name, "digest", (int) nonceGCInterval);
2d70df72 571}
572
f5691f9c 573bool
574AuthDigestConfig::active() const
2d70df72 575{
f5691f9c 576 return authdigest_initialised == 1;
2d70df72 577}
62e76326 578
f5691f9c 579bool
580AuthDigestConfig::configured() const
2d70df72 581{
f5691f9c 582 if ((authenticate != NULL) &&
48d54e4d 583 (authenticateChildren.n_max != 0) &&
f5691f9c 584 (digestAuthRealm != NULL) && (noncemaxduration > -1))
585 return true;
62e76326 586
f5691f9c 587 return false;
2d70df72 588}
589
82b045dc 590int
f5691f9c 591AuthDigestUserRequest::authenticated() const
2d70df72 592{
82b045dc 593 if (credentials() == Ok)
62e76326 594 return 1;
82b045dc 595
596 return 0;
2d70df72 597}
598
e1f7507e 599/** log a digest user in
2d70df72 600 */
82b045dc 601void
486bf0fb 602AuthDigestUserRequest::authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type)
2d70df72 603{
e1f7507e 604 AuthUser *auth_user;
f5691f9c 605 AuthDigestUserRequest *digest_request;
2d70df72 606 digest_user_h *digest_user;
607
608 HASHHEX SESSIONKEY;
609 HASHHEX HA2 = "";
610 HASHHEX Response;
611
82b045dc 612 assert(authUser() != NULL);
613 auth_user = authUser();
2d70df72 614
f5691f9c 615 digest_user = dynamic_cast < digest_user_h * >(auth_user);
2d70df72 616
7da7b01f 617 assert(digest_user != NULL);
618
9bea1d5b 619 /* if the check has corrupted the user, just return */
62e76326 620
82b045dc 621 if (credentials() == Failed) {
62e76326 622 return;
bd507204 623 }
62e76326 624
82b045dc 625 digest_request = this;
2d70df72 626
627 /* do we have the HA1 */
62e76326 628
2d70df72 629 if (!digest_user->HA1created) {
82b045dc 630 credentials(Pending);
62e76326 631 return;
2d70df72 632 }
62e76326 633
2d70df72 634 if (digest_request->nonce == NULL) {
62e76326 635 /* this isn't a nonce we issued */
82b045dc 636 credentials(Failed);
62e76326 637 return;
2d70df72 638 }
62e76326 639
2d70df72 640 DigestCalcHA1(digest_request->algorithm, NULL, NULL, NULL,
62e76326 641 authenticateDigestNonceNonceb64(digest_request->nonce),
642 digest_request->cnonce,
643 digest_user->HA1, SESSIONKEY);
2d70df72 644 DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce),
62e76326 645 digest_request->nc, digest_request->cnonce, digest_request->qop,
60745f24 646 RequestMethodStr(request->method), digest_request->uri, HA2, Response);
2d70df72 647
bf8fe701 648 debugs(29, 9, "\nResponse = '" << digest_request->response << "'\nsquid is = '" << Response << "'");
2d70df72 649
df80d445 650 if (strcasecmp(digest_request->response, Response) != 0) {
651 if (!digest_request->flags.helper_queried) {
652 /* Query the helper in case the password has changed */
653 digest_request->flags.helper_queried = 1;
654 digest_request->credentials_ok = Pending;
655 return;
656 }
657
f5691f9c 658 if (digestConfig.PostWorkaround && request->method != METHOD_GET) {
f5292c64 659 /* Ugly workaround for certain very broken browsers using the
660 * wrong method to calculate the request-digest on POST request.
661 * This should be deleted once Digest authentication becomes more
662 * widespread and such broken browsers no longer are commonly
663 * used.
664 */
665 DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce),
666 digest_request->nc, digest_request->cnonce, digest_request->qop,
60745f24 667 RequestMethodStr(METHOD_GET), digest_request->uri, HA2, Response);
f5292c64 668
669 if (strcasecmp(digest_request->response, Response)) {
670 credentials(Failed);
0a0c70cd 671 digest_request->setDenyMessage("Incorrect password");
f5292c64 672 return;
673 } else {
a9925b40 674 const char *useragent = request->header.getStr(HDR_USER_AGENT);
f5292c64 675
f7fbb7a5 676 static IpAddress last_broken_addr;
c3edaed6 677 static int seen_broken_client = 0;
678
679 if (!seen_broken_client) {
cc192b50 680 last_broken_addr.SetNoAddr();
c3edaed6 681 seen_broken_client = 1;
682 }
f5292c64 683
cc192b50 684 if (last_broken_addr != request->client_addr) {
bf8fe701 685 debugs(29, 1, "\nDigest POST bug detected from " <<
cc192b50 686 request->client_addr << " using '" <<
bf8fe701 687 (useragent ? useragent : "-") <<
688 "'. Please upgrade browser. See Bug #630 for details.");
689
f5292c64 690 last_broken_addr = request->client_addr;
691 }
692 }
693 } else {
694 credentials(Failed);
9f709824 695 digest_request->flags.invalid_password = 1;
0a0c70cd 696 digest_request->setDenyMessage("Incorrect password");
f5292c64 697 return;
698 }
699
700 /* check for stale nonce */
701 if (!authDigestNonceIsValid(digest_request->nonce, digest_request->nc)) {
bf8fe701 702 debugs(29, 3, "authenticateDigestAuthenticateuser: user '" << digest_user->username() << "' validated OK but nonce stale");
f5292c64 703 credentials(Failed);
0a0c70cd 704 digest_request->setDenyMessage("Stale nonce");
f5292c64 705 return;
706 }
2d70df72 707 }
62e76326 708
82b045dc 709 credentials(Ok);
f5292c64 710
2d70df72 711 /* password was checked and did match */
bf8fe701 712 debugs(29, 4, "authenticateDigestAuthenticateuser: user '" << digest_user->username() << "' validated OK");
2d70df72 713
714 /* auth_user is now linked, we reset these values
715 * after external auth occurs anyway */
716 auth_user->expiretime = current_time.tv_sec;
2d70df72 717 return;
718}
719
82b045dc 720int
f5691f9c 721AuthDigestUserRequest::module_direction()
2d70df72 722{
82b045dc 723 switch (credentials()) {
62e76326 724
82b045dc 725 case Unchecked:
62e76326 726 return -1;
727
82b045dc 728 case Ok:
62e76326 729
62e76326 730 return 0;
731
82b045dc 732 case Pending:
62e76326 733 return -1;
734
82b045dc 735 case Failed:
f5292c64 736
26ac0430
AJ
737 /* send new challenge */
738 return 1;
2d70df72 739 }
62e76326 740
2d70df72 741 return -2;
742}
743
744/* add the [proxy]authorisation header */
82b045dc 745void
f5691f9c 746AuthDigestUserRequest::addHeader(HttpReply * rep, int accel)
2d70df72 747{
6e3d4bbc 748 http_hdr_type type;
62e76326 749
2d70df72 750 /* don't add to authentication error pages */
82b045dc 751
2d70df72 752 if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED)
62e76326 753 || (accel && rep->sline.status == HTTP_UNAUTHORIZED))
754 return;
755
2d70df72 756 type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
757
758#if WAITING_FOR_TE
759 /* test for http/1.1 transfer chunked encoding */
760 if (chunkedtest)
62e76326 761 return;
762
2d70df72 763#endif
764
f5691f9c 765 if ((digestConfig.authenticate) && authDigestNonceLastRequest(nonce)) {
82b045dc 766 flags.authinfo_sent = 1;
bf8fe701 767 debugs(29, 9, "authDigestAddHead: Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce) << "\"");
82b045dc 768 httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce));
2d70df72 769 }
770}
771
772#if WAITING_FOR_TE
773/* add the [proxy]authorisation header */
f5691f9c 774void
775AuthDigestUserRequest::addTrailer(HttpReply * rep, int accel)
2d70df72 776{
777 int type;
62e76326 778
2d70df72 779 if (!auth_user_request)
62e76326 780 return;
781
62e76326 782
2d70df72 783 /* has the header already been send? */
f5691f9c 784 if (flags.authinfo_sent)
62e76326 785 return;
786
2d70df72 787 /* don't add to authentication error pages */
788 if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED)
62e76326 789 || (accel && rep->sline.status == HTTP_UNAUTHORIZED))
790 return;
791
2d70df72 792 type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
793
f5691f9c 794 if ((digestConfig.authenticate) && authDigestNonceLastRequest(nonce)) {
bf8fe701 795 debugs(29, 9, "authDigestAddTrailer: Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce) << "\"");
f5691f9c 796 httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce));
2d70df72 797 }
798}
62e76326 799
2d70df72 800#endif
801
802/* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
803void
18ec8500 804AuthDigestConfig::fixHeader(AuthUserRequest *auth_user_request, HttpReply *rep, http_hdr_type hdrType, HttpRequest * request)
2d70df72 805{
f5691f9c 806 if (!authenticate)
82b045dc 807 return;
808
a598b010 809 int stale = 0;
62e76326 810
f5691f9c 811 if (auth_user_request) {
812 AuthDigestUserRequest *digest_request;
813 digest_request = dynamic_cast < AuthDigestUserRequest * >(auth_user_request);
7da7b01f 814 assert (digest_request != NULL);
62e76326 815
9f709824 816 stale = !digest_request->flags.invalid_password;
2d70df72 817 }
82b045dc 818
819 /* on a 407 or 401 we always use a new nonce */
820 digest_nonce_h *nonce = authenticateDigestNonceNew();
821
18ec8500 822 debugs(29, 9, "authenticateFixHeader: Sending type:" << hdrType <<
bf8fe701 823 " header: 'Digest realm=\"" << digestAuthRealm << "\", nonce=\"" <<
824 authenticateDigestNonceNonceb64(nonce) << "\", qop=\"" << QOP_AUTH <<
825 "\", stale=" << (stale ? "true" : "false"));
82b045dc 826
827 /* in the future, for WWW auth we may want to support the domain entry */
18ec8500 828 httpHeaderPutStrf(&rep->header, hdrType, "Digest realm=\"%s\", nonce=\"%s\", qop=\"%s\", stale=%s", digestAuthRealm, authenticateDigestNonceNonceb64(nonce), QOP_AUTH, stale ? "true" : "false");
2d70df72 829}
830
f5691f9c 831DigestUser::~DigestUser()
2d70df72 832{
82b045dc 833
834 dlink_node *link, *tmplink;
835 link = nonces.head;
62e76326 836
2d70df72 837 while (link) {
62e76326 838 tmplink = link;
839 link = link->next;
82b045dc 840 dlinkDelete(tmplink, &nonces);
62e76326 841 authDigestNoncePurge(static_cast < digest_nonce_h * >(tmplink->data));
842 authDigestNonceUnlink(static_cast < digest_nonce_h * >(tmplink->data));
843 dlinkNodeDelete(tmplink);
2d70df72 844 }
2d70df72 845}
846
847static void
848authenticateDigestHandleReply(void *data, char *reply)
849{
82b045dc 850 DigestAuthenticateStateData *replyData = static_cast < DigestAuthenticateStateData * >(data);
76f142cd 851 AuthUserRequest *auth_user_request;
f5691f9c 852 AuthDigestUserRequest *digest_request;
2d70df72 853 digest_user_h *digest_user;
2d70df72 854 char *t = NULL;
fa80a8ef 855 void *cbdata;
bf8fe701 856 debugs(29, 9, "authenticateDigestHandleReply: {" << (reply ? reply : "<NULL>") << "}");
62e76326 857
2d70df72 858 if (reply) {
62e76326 859 if ((t = strchr(reply, ' ')))
0a0c70cd 860 *t++ = '\0';
62e76326 861
778c696f 862 if (*reply == '\0' || *reply == '\n')
62e76326 863 reply = NULL;
2d70df72 864 }
62e76326 865
82b045dc 866 assert(replyData->auth_user_request != NULL);
867 auth_user_request = replyData->auth_user_request;
f5691f9c 868 digest_request = dynamic_cast < AuthDigestUserRequest * >(auth_user_request);
869 assert(digest_request);
7da7b01f 870
f5691f9c 871 digest_user = dynamic_cast < digest_user_h * >(auth_user_request->user());
7da7b01f 872 assert(digest_user != NULL);
62e76326 873
0a0c70cd 874 if (reply && (strncasecmp(reply, "ERR", 3) == 0)) {
f5691f9c 875 digest_request->credentials(AuthDigestUserRequest::Failed);
26ac0430 876 digest_request->flags.invalid_password = 1;
0a0c70cd 877
878 if (t && *t)
879 digest_request->setDenyMessage(t);
880 } else if (reply) {
62e76326 881 CvtBin(reply, digest_user->HA1);
882 digest_user->HA1created = 1;
2d70df72 883 }
62e76326 884
82b045dc 885 if (cbdataReferenceValidDone(replyData->data, &cbdata))
886 replyData->handler(cbdata, NULL);
62e76326 887
8b45a856 888 //we know replyData->auth_user_request != NULL, or we'd have asserted
4f0ef8e8 889 AUTHUSERREQUESTUNLOCK(replyData->auth_user_request, "replyData");
8b45a856 890
82b045dc 891 cbdataFree(replyData);
2d70df72 892}
893
894/* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
895 * config file */
f5691f9c 896void
897AuthDigestConfig::init(AuthConfig * scheme)
2d70df72 898{
f5691f9c 899 if (authenticate) {
6d97f5f1 900 DigestFieldsInfo = httpHeaderBuildFieldsInfo(DigestAttrs, DIGEST_ENUM_END);
62e76326 901 authenticateDigestNonceSetup();
902 authdigest_initialised = 1;
903
904 if (digestauthenticators == NULL)
48d54e4d 905 digestauthenticators = new helper("digestauthenticator");
62e76326 906
f5691f9c 907 digestauthenticators->cmdline = authenticate;
62e76326 908
48d54e4d 909 digestauthenticators->childs = authenticateChildren;
62e76326 910
911 digestauthenticators->ipc_type = IPC_STREAM;
912
913 helperOpenServers(digestauthenticators);
914
62e76326 915 CBDATA_INIT_TYPE(DigestAuthenticateStateData);
2d70df72 916 }
917}
918
62ee09ca 919void
15fab853 920AuthDigestConfig::registerWithCacheManager(void)
62ee09ca 921{
15fab853 922 CacheManager::GetInstance()->
26ac0430
AJ
923 registerAction("digestauthenticator",
924 "Digest User Authenticator Stats",
925 authenticateDigestStats, 0, 1);
62ee09ca 926}
2d70df72 927
928/* free any allocated configuration details */
929void
f5691f9c 930AuthDigestConfig::done()
2d70df72 931{
f5691f9c 932 if (authenticate)
933 wordlistDestroy(&authenticate);
62e76326 934
f5691f9c 935 safe_free(digestAuthRealm);
936}
62e76326 937
b23c9d89 938AuthDigestConfig::AuthDigestConfig() : authenticateChildren(20)
f5691f9c 939{
940 /* TODO: move into initialisation list */
f5691f9c 941 /* 5 minutes */
942 nonceGCInterval = 5 * 60;
943 /* 30 minutes */
944 noncemaxduration = 30 * 60;
945 /* 50 requests */
946 noncemaxuses = 50;
947 /* Not strict nonce count behaviour */
948 NonceStrictness = 0;
949 /* Verify nonce count */
950 CheckNonceCount = 1;
2d70df72 951}
952
f5691f9c 953void
954AuthDigestConfig::parse(AuthConfig * scheme, int n_configured, char *param_str)
2d70df72 955{
2d70df72 956 if (strcasecmp(param_str, "program") == 0) {
f5691f9c 957 if (authenticate)
958 wordlistDestroy(&authenticate);
62e76326 959
f5691f9c 960 parse_wordlist(&authenticate);
62e76326 961
42900318 962 requirePathnameExists("auth_param digest program", authenticate->key);
2d70df72 963 } else if (strcasecmp(param_str, "children") == 0) {
48d54e4d 964 authenticateChildren.parseConfig();
2d70df72 965 } else if (strcasecmp(param_str, "realm") == 0) {
f5691f9c 966 parse_eol(&digestAuthRealm);
2d70df72 967 } else if (strcasecmp(param_str, "nonce_garbage_interval") == 0) {
f5691f9c 968 parse_time_t(&nonceGCInterval);
2d70df72 969 } else if (strcasecmp(param_str, "nonce_max_duration") == 0) {
f5691f9c 970 parse_time_t(&noncemaxduration);
2d70df72 971 } else if (strcasecmp(param_str, "nonce_max_count") == 0) {
f5691f9c 972 parse_int((int *) &noncemaxuses);
d205783b 973 } else if (strcasecmp(param_str, "nonce_strictness") == 0) {
f5691f9c 974 parse_onoff(&NonceStrictness);
f5292c64 975 } else if (strcasecmp(param_str, "check_nonce_count") == 0) {
f5691f9c 976 parse_onoff(&CheckNonceCount);
f5292c64 977 } else if (strcasecmp(param_str, "post_workaround") == 0) {
f5691f9c 978 parse_onoff(&PostWorkaround);
f741d2f6
HN
979 } else if (strcasecmp(param_str, "utf8") == 0) {
980 parse_onoff(&utf8);
2d70df72 981 } else {
bf8fe701 982 debugs(29, 0, "unrecognised digest auth scheme parameter '" << param_str << "'");
2d70df72 983 }
984}
985
f5691f9c 986const char *
987AuthDigestConfig::type() const
988{
989 return digestScheme::GetInstance().type();
990}
991
2d70df72 992
993static void
994authenticateDigestStats(StoreEntry * sentry)
995{
9522b380 996 helperStats(sentry, digestauthenticators, "Digest Authenticator Statistics");
2d70df72 997}
998
999/* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
1000
c78aa667 1001static void
2d70df72 1002authDigestNonceUserUnlink(digest_nonce_h * nonce)
1003{
1004 digest_user_h *digest_user;
1005 dlink_node *link, *tmplink;
62e76326 1006
2d70df72 1007 if (!nonce)
62e76326 1008 return;
1009
f5691f9c 1010 if (!nonce->user)
62e76326 1011 return;
1012
f5691f9c 1013 digest_user = nonce->user;
62e76326 1014
1015 /* unlink from the user list. Yes we're crossing structures but this is the only
2d70df72 1016 * time this code is needed
1017 */
1018 link = digest_user->nonces.head;
62e76326 1019
2d70df72 1020 while (link) {
62e76326 1021 tmplink = link;
1022 link = link->next;
1023
1024 if (tmplink->data == nonce) {
1025 dlinkDelete(tmplink, &digest_user->nonces);
1026 authDigestNonceUnlink(static_cast < digest_nonce_h * >(tmplink->data));
1027 dlinkNodeDelete(tmplink);
1028 link = NULL;
1029 }
2d70df72 1030 }
62e76326 1031
f5691f9c 1032 /* this reference to user was not locked because freeeing the user frees
26ac0430 1033 * the nonce too.
2d70df72 1034 */
f5691f9c 1035 nonce->user = NULL;
2d70df72 1036}
1037
1038/* authDigestUserLinkNonce: add a nonce to a given user's struct */
1039
c78aa667 1040static void
f5691f9c 1041authDigestUserLinkNonce(DigestUser * user, digest_nonce_h * nonce)
2d70df72 1042{
1043 dlink_node *node;
1044 digest_user_h *digest_user;
62e76326 1045
f5691f9c 1046 if (!user || !nonce)
62e76326 1047 return;
1048
f5691f9c 1049 digest_user = user;
62e76326 1050
2d70df72 1051 node = digest_user->nonces.head;
62e76326 1052
2d70df72 1053 while (node && (node->data != nonce))
62e76326 1054 node = node->next;
1055
2d70df72 1056 if (node)
62e76326 1057 return;
1058
2d70df72 1059 node = dlinkNodeNew();
62e76326 1060
2d70df72 1061 dlinkAddTail(nonce, node, &digest_user->nonces);
62e76326 1062
2d70df72 1063 authDigestNonceLink(nonce);
62e76326 1064
2d70df72 1065 /* ping this nonce to this auth user */
4e9d4067 1066 assert((nonce->user == NULL) || (nonce->user == user));
62e76326 1067
f5691f9c 1068 /* we don't lock this reference because removing the user removes the
2d70df72 1069 * hash too. Of course if that changes we're stuffed so read the code huh?
1070 */
f5691f9c 1071 nonce->user = user;
2d70df72 1072}
1073
1074/* setup the necessary info to log the username */
f5691f9c 1075static AuthUserRequest *
9f709824 1076authDigestLogUsername(char *username, AuthDigestUserRequest *auth_user_request)
2d70df72 1077{
f5691f9c 1078 assert(auth_user_request != NULL);
2d70df72 1079
1080 /* log the username */
bf8fe701 1081 debugs(29, 9, "authDigestLogUsername: Creating new user for logging '" << username << "'");
f5691f9c 1082 digest_user_h *digest_user = new DigestUser(&digestConfig);
2d70df72 1083 /* save the credentials */
f5691f9c 1084 digest_user->username(username);
2d70df72 1085 /* set the auth_user type */
f5691f9c 1086 digest_user->auth_type = AUTH_BROKEN;
2d70df72 1087 /* link the request to the user */
f5691f9c 1088 auth_user_request->authUser(digest_user);
1089 auth_user_request->user(digest_user);
1090 digest_user->addRequest (auth_user_request);
1091 return auth_user_request;
2d70df72 1092}
1093
1094/*
1095 * Decode a Digest [Proxy-]Auth string, placing the results in the passed
1096 * Auth_user structure.
1097 */
f5691f9c 1098AuthUserRequest *
1099AuthDigestConfig::decode(char const *proxy_auth)
2d70df72 1100{
2d70df72 1101 const char *item;
1102 const char *p;
1103 const char *pos = NULL;
1104 char *username = NULL;
1105 digest_nonce_h *nonce;
63c5fa4a 1106 int ilen;
2d70df72 1107
bf8fe701 1108 debugs(29, 9, "authenticateDigestDecodeAuth: beginning");
2d70df72 1109
f5691f9c 1110 AuthDigestUserRequest *digest_request = new AuthDigestUserRequest();
2d70df72 1111
1112 /* trim DIGEST from string */
62e76326 1113
ba53f4b8 1114 while (xisgraph(*proxy_auth))
62e76326 1115 proxy_auth++;
2d70df72 1116
1117 /* Trim leading whitespace before decoding */
1118 while (xisspace(*proxy_auth))
62e76326 1119 proxy_auth++;
2d70df72 1120
30abd221 1121 String temp(proxy_auth);
62e76326 1122
2d70df72 1123 while (strListGetItem(&temp, ',', &item, &ilen, &pos)) {
6d97f5f1
A
1124 String value;
1125 size_t nlen;
1126 /* isolate directive name */
1127 if ((p = (const char *)memchr(item, '=', ilen)) && (p - item < ilen)) {
9abd1514 1128 nlen = p++ - item;
6d97f5f1
A
1129 if (!httpHeaderParseQuotedString(p, &value))
1130 value.limitInit(p, ilen - (p - item));
1131 } else
1132 nlen = ilen;
1133
1134 if (!value.defined()) {
1135 debugs(29, 9, "authDigestDecodeAuth: Failed to parse attribute '" << temp << "' in '" << proxy_auth << "'");
1136 continue;
1137 }
9abd1514 1138
6d97f5f1
A
1139 /* find type */
1140 http_digest_attr_type type = (http_digest_attr_type)httpHeaderIdByName(item, nlen, DigestFieldsInfo, DIGEST_ENUM_END);
9abd1514 1141
6d97f5f1
A
1142 switch (type) {
1143 case DIGEST_USERNAME:
bbe0ed86 1144 safe_free(username);
9abd1514 1145 username = xstrndup(value.rawBuf(), value.size() + 1);
bf8fe701 1146 debugs(29, 9, "authDigestDecodeAuth: Found Username '" << username << "'");
6d97f5f1 1147 break;
62e76326 1148
6d97f5f1 1149 case DIGEST_REALM:
bbe0ed86 1150 safe_free(digest_request->realm);
9abd1514 1151 digest_request->realm = xstrndup(value.rawBuf(), value.size() + 1);
bf8fe701 1152 debugs(29, 9, "authDigestDecodeAuth: Found realm '" << digest_request->realm << "'");
6d97f5f1 1153 break;
62e76326 1154
6d97f5f1 1155 case DIGEST_QOP:
bbe0ed86 1156 safe_free(digest_request->qop);
9abd1514 1157 digest_request->qop = xstrndup(value.rawBuf(), value.size() + 1);
bf8fe701 1158 debugs(29, 9, "authDigestDecodeAuth: Found qop '" << digest_request->qop << "'");
6d97f5f1 1159 break;
62e76326 1160
6d97f5f1 1161 case DIGEST_ALGORITHM:
bbe0ed86 1162 safe_free(digest_request->algorithm);
9abd1514 1163 digest_request->algorithm = xstrndup(value.rawBuf(), value.size() + 1);
bf8fe701 1164 debugs(29, 9, "authDigestDecodeAuth: Found algorithm '" << digest_request->algorithm << "'");
6d97f5f1 1165 break;
62e76326 1166
6d97f5f1 1167 case DIGEST_URI:
bbe0ed86 1168 safe_free(digest_request->uri);
9abd1514 1169 digest_request->uri = xstrndup(value.rawBuf(), value.size() + 1);
bf8fe701 1170 debugs(29, 9, "authDigestDecodeAuth: Found uri '" << digest_request->uri << "'");
6d97f5f1 1171 break;
62e76326 1172
6d97f5f1 1173 case DIGEST_NONCE:
bbe0ed86 1174 safe_free(digest_request->nonceb64);
9abd1514 1175 digest_request->nonceb64 = xstrndup(value.rawBuf(), value.size() + 1);
bf8fe701 1176 debugs(29, 9, "authDigestDecodeAuth: Found nonce '" << digest_request->nonceb64 << "'");
6d97f5f1 1177 break;
62e76326 1178
6d97f5f1
A
1179 case DIGEST_NC:
1180 if (value.size() != 8) {
1181 debugs(29, 9, "authDigestDecodeAuth: Invalid nc '" << value << "' in '" << temp << "'");
1182 }
9abd1514 1183 xstrncpy(digest_request->nc, value.rawBuf(), value.size() + 1);
bf8fe701 1184 debugs(29, 9, "authDigestDecodeAuth: Found noncecount '" << digest_request->nc << "'");
6d97f5f1 1185 break;
62e76326 1186
6d97f5f1 1187 case DIGEST_CNONCE:
bbe0ed86 1188 safe_free(digest_request->cnonce);
9abd1514 1189 digest_request->cnonce = xstrndup(value.rawBuf(), value.size() + 1);
bf8fe701 1190 debugs(29, 9, "authDigestDecodeAuth: Found cnonce '" << digest_request->cnonce << "'");
6d97f5f1 1191 break;
62e76326 1192
6d97f5f1 1193 case DIGEST_RESPONSE:
bbe0ed86 1194 safe_free(digest_request->response);
9abd1514 1195 digest_request->response = xstrndup(value.rawBuf(), value.size() + 1);
bf8fe701 1196 debugs(29, 9, "authDigestDecodeAuth: Found response '" << digest_request->response << "'");
6d97f5f1 1197 break;
9abd1514 1198
6d97f5f1 1199 default:
59a98343 1200 debugs(29, 3, "authDigestDecodeAuth: Unknown attribute '" << item << "' in '" << temp << "'");
9abd1514 1201
62e76326 1202 }
2d70df72 1203 }
62e76326 1204
30abd221 1205 temp.clean();
2d70df72 1206
1207
1208 /* now we validate the data given to us */
1209
74830fc8 1210 /*
1211 * TODO: on invalid parameters we should return 400, not 407.
1212 * Find some clean way of doing this. perhaps return a valid
1213 * struct, and set the direction to clientwards combined with
1214 * a change to the clientwards handling code (ie let the
1215 * clientwards call set the error type (but limited to known
1216 * correct values - 400/401/407
1217 */
2d70df72 1218
59a98343 1219 /* 2069 requirements */
62e76326 1220
59a98343
HN
1221 /* do we have a username ? */
1222 if (!username || username[0] == '\0') {
1223 debugs(29, 2, "authenticateDigestDecode: Empty or not present username");
9f709824 1224 return authDigestLogUsername(username, digest_request);
2d70df72 1225 }
62e76326 1226
59a98343
HN
1227 /* do we have a realm ? */
1228 if (!digest_request->realm || digest_request->realm[0] == '\0') {
1229 debugs(29, 2, "authenticateDigestDecode: Empty or not present realm");
9f709824 1230 return authDigestLogUsername(username, digest_request);
2d70df72 1231 }
62e76326 1232
59a98343
HN
1233 /* and a nonce? */
1234 if (!digest_request->nonceb64 || digest_request->nonceb64[0] == '\0') {
1235 debugs(29, 2, "authenticateDigestDecode: Empty or not present nonce");
9f709824 1236 return authDigestLogUsername(username, digest_request);
2d70df72 1237 }
62e76326 1238
74830fc8 1239 /* we can't check the URI just yet. We'll check it in the
6649f955 1240 * authenticate phase, but needs to be given */
59a98343
HN
1241 if (!digest_request->uri || digest_request->uri[0] == '\0') {
1242 debugs(29, 2, "authenticateDigestDecode: Missing URI field");
6649f955
HN
1243 return authDigestLogUsername(username, digest_request);
1244 }
2d70df72 1245
1246 /* is the response the correct length? */
2d70df72 1247 if (!digest_request->response || strlen(digest_request->response) != 32) {
59a98343 1248 debugs(29, 2, "authenticateDigestDecode: Response length invalid");
9f709824 1249 return authDigestLogUsername(username, digest_request);
2d70df72 1250 }
62e76326 1251
59a98343
HN
1252 /* check the algorithm is present and supported */
1253 if (!digest_request->algorithm)
1254 digest_request->algorithm = xstrndup("MD5", 4);
1255 else if (strcmp(digest_request->algorithm, "MD5")
1256 && strcmp(digest_request->algorithm, "MD5-sess")) {
d6b7a3c4 1257 debugs(29, 2, "authenticateDigestDecode: invalid algorithm specified!");
9f709824 1258 return authDigestLogUsername(username, digest_request);
2d70df72 1259 }
62e76326 1260
59a98343
HN
1261 /* 2617 requirements, indicated by qop */
1262 if (digest_request->qop) {
1263
6d97f5f1
A
1264 /* check the qop is what we expected. */
1265 if (strcmp(digest_request->qop, QOP_AUTH) != 0) {
1266 /* we received a qop option we didn't send */
1267 debugs(29, 2, "authenticateDigestDecode: Invalid qop option received");
1268 return authDigestLogUsername(username, digest_request);
1269 }
1270
1271 /* check cnonce */
1272 if (!digest_request->cnonce || digest_request->cnonce[0] == '\0') {
1273 debugs(29, 2, "authenticateDigestDecode: Missing cnonce field");
1274 return authDigestLogUsername(username, digest_request);
1275 }
1276
1277 /* check nc */
1278 if (strlen(digest_request->nc) != 8 || strspn(digest_request->nc, "0123456789abcdefABCDEF") != 8) {
1279 debugs(29, 2, "authenticateDigestDecode: invalid nonce count");
1280 return authDigestLogUsername(username, digest_request);
1281 }
59a98343 1282 } else {
6d97f5f1
A
1283 /* cnonce and nc both require qop */
1284 if (digest_request->cnonce || digest_request->nc) {
1285 debugs(29, 2, "authenticateDigestDecode: missing qop!");
1286 return authDigestLogUsername(username, digest_request);
1287 }
2d70df72 1288 }
62e76326 1289
59a98343
HN
1290 /** below nonce state dependent **/
1291
1292 /* now the nonce */
1293 nonce = authenticateDigestNonceFindNonce(digest_request->nonceb64);
1294 if (!nonce) {
1295 /* we couldn't find a matching nonce! */
1296 debugs(29, 2, "authenticateDigestDecode: Unexpected or invalid nonce received");
fa004391 1297 digest_request->credentials(AuthDigestUserRequest::Failed);
9f709824 1298 return authDigestLogUsername(username, digest_request);
2d70df72 1299 }
62e76326 1300
59a98343
HN
1301 digest_request->nonce = nonce;
1302 authDigestNonceLink(nonce);
1303
1304 /* check that we're not being hacked / the username hasn't changed */
1305 if (nonce->user && strcmp(username, nonce->user->username())) {
1306 debugs(29, 2, "authenticateDigestDecode: Username for the nonce does not equal the username for the request");
9f709824 1307 return authDigestLogUsername(username, digest_request);
2d70df72 1308 }
62e76326 1309
2d70df72 1310 /* the method we'll check at the authenticate step as well */
1311
1312
1313 /* we don't send or parse opaques. Ok so we're flexable ... */
1314
1315 /* find the user */
f5691f9c 1316 digest_user_h *digest_user;
1317
e1f7507e 1318 AuthUser *auth_user;
2d70df72 1319
1320 if ((auth_user = authDigestUserFindUsername(username)) == NULL) {
62e76326 1321 /* the user doesn't exist in the username cache yet */
bf8fe701 1322 debugs(29, 9, "authDigestDecodeAuth: Creating new digest user '" << username << "'");
f5691f9c 1323 digest_user = new DigestUser (&digestConfig);
1324 /* auth_user is a parent */
1325 auth_user = digest_user;
62e76326 1326 /* save the username */
f5691f9c 1327 digest_user->username(username);
62e76326 1328 /* set the user type */
f5691f9c 1329 digest_user->auth_type = AUTH_DIGEST;
62e76326 1330 /* this auth_user struct is the one to get added to the
1331 * username cache */
1332 /* store user in hash's */
f5691f9c 1333 digest_user->addToNameCache();
df80d445 1334
62e76326 1335 /*
1336 * Add the digest to the user so we can tell if a hacking
1337 * or spoofing attack is taking place. We do this by assuming
1338 * the user agent won't change user name without warning.
1339 */
f5691f9c 1340 authDigestUserLinkNonce(digest_user, nonce);
2d70df72 1341 } else {
bf8fe701 1342 debugs(29, 9, "authDigestDecodeAuth: Found user '" << username << "' in the user cache as '" << auth_user << "'");
f5691f9c 1343 digest_user = static_cast < digest_user_h * >(auth_user);
62e76326 1344 xfree(username);
2d70df72 1345 }
62e76326 1346
2d70df72 1347 /*link the request and the user */
f5691f9c 1348 assert(digest_request != NULL);
82b045dc 1349
f5691f9c 1350 digest_request->authUser (digest_user);
62e76326 1351
f5691f9c 1352 digest_request->user(digest_user);
62e76326 1353
f5691f9c 1354 digest_user->addRequest (digest_request);
2d70df72 1355
bf8fe701 1356 debugs(29, 9, "username = '" << digest_user->username() << "'\nrealm = '" <<
1357 digest_request->realm << "'\nqop = '" << digest_request->qop <<
1358 "'\nalgorithm = '" << digest_request->algorithm << "'\nuri = '" <<
1359 digest_request->uri << "'\nnonce = '" << digest_request->nonceb64 <<
1360 "'\nnc = '" << digest_request->nc << "'\ncnonce = '" <<
1361 digest_request->cnonce << "'\nresponse = '" <<
1362 digest_request->response << "'\ndigestnonce = '" << nonce << "'");
2d70df72 1363
f5691f9c 1364 return digest_request;
2d70df72 1365}
1366
1367/* send the initial data to a digest authenticator module */
f5691f9c 1368void
1369AuthDigestUserRequest::module_start(RH * handler, void *data)
2d70df72 1370{
e6ccf245 1371 DigestAuthenticateStateData *r = NULL;
2d70df72 1372 char buf[8192];
2d70df72 1373 digest_user_h *digest_user;
f5691f9c 1374 assert(user()->auth_type == AUTH_DIGEST);
1375 digest_user = dynamic_cast < digest_user_h * >(user());
7da7b01f 1376 assert(digest_user != NULL);
bf8fe701 1377 debugs(29, 9, "authenticateStart: '\"" << digest_user->username() << "\":\"" << realm << "\"'");
62e76326 1378
f5691f9c 1379 if (digestConfig.authenticate == NULL) {
62e76326 1380 handler(data, NULL);
1381 return;
2d70df72 1382 }
62e76326 1383
e6ccf245 1384 r = cbdataAlloc(DigestAuthenticateStateData);
2d70df72 1385 r->handler = handler;
fa80a8ef 1386 r->data = cbdataReference(data);
f5691f9c 1387 r->auth_user_request = this;
4f0ef8e8 1388 AUTHUSERREQUESTLOCK(r->auth_user_request, "r");
f741d2f6 1389 if (digestConfig.utf8) {
076df709
FC
1390 char userstr[1024];
1391 latin1_to_utf8(userstr, sizeof(userstr), digest_user->username());
1392 snprintf(buf, 8192, "\"%s\":\"%s\"\n", userstr, realm);
f741d2f6 1393 } else {
26ac0430 1394 snprintf(buf, 8192, "\"%s\":\"%s\"\n", digest_user->username(), realm);
f741d2f6 1395 }
8b45a856 1396
2d70df72 1397 helperSubmit(digestauthenticators, buf, authenticateDigestHandleReply, r);
1398}
82b045dc 1399
076df709 1400DigestUser::DigestUser (AuthConfig *aConfig) : AuthUser (aConfig), HA1created (0)
82b045dc 1401{}
1402
e1f7507e 1403AuthUser *
f5691f9c 1404AuthDigestUserRequest::authUser() const
82b045dc 1405{
f5691f9c 1406 return const_cast<AuthUser *>(user());
82b045dc 1407}
1408
1409void
e1f7507e 1410AuthDigestUserRequest::authUser(AuthUser *aUser)
82b045dc 1411{
e1f7507e 1412 assert(!authUser());
f5691f9c 1413 user(aUser);
e1f7507e 1414 user()->lock();
82b045dc 1415}
1416
f5691f9c 1417AuthDigestUserRequest::CredentialsState
1418AuthDigestUserRequest::credentials() const
1419{
1420 return credentials_ok;
f5691f9c 1421}
82b045dc 1422
f5691f9c 1423void
1424AuthDigestUserRequest::credentials(CredentialsState newCreds)
82b045dc 1425{
f5691f9c 1426 credentials_ok = newCreds;
82b045dc 1427}
1428
f5691f9c 1429AuthDigestUserRequest::AuthDigestUserRequest() : nonceb64(NULL) ,cnonce(NULL) ,realm(NULL),
1430 pszPass(NULL) ,algorithm(NULL) ,pszMethod(NULL),
1431 qop(NULL) ,uri(NULL) ,response(NULL),
1432 nonce(NULL), _theUser (NULL) ,
1433 credentials_ok (Unchecked)
1434{}
1435
e1f7507e 1436/** delete the digest request structure. Does NOT delete related structures */
f5691f9c 1437AuthDigestUserRequest::~AuthDigestUserRequest()
82b045dc 1438{
f5691f9c 1439 safe_free (nonceb64);
1440 safe_free (cnonce);
1441 safe_free (realm);
1442 safe_free (pszPass);
1443 safe_free (algorithm);
1444 safe_free (pszMethod);
1445 safe_free (qop);
1446 safe_free (uri);
1447 safe_free (response);
1448
1449 if (nonce)
1450 authDigestNonceUnlink(nonce);
82b045dc 1451}
1452
f5691f9c 1453AuthConfig *
1454digestScheme::createConfig()
82b045dc 1455{
f5691f9c 1456 return &digestConfig;
82b045dc 1457}
f5691f9c 1458