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