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