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