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