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