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