]>
Commit | Line | Data |
---|---|---|
bbc27441 | 1 | /* |
f6e9a3ee | 2 | * Copyright (C) 1996-2019 The Squid Software Foundation and contributors |
bbc27441 AJ |
3 | * |
4 | * Squid software is distributed under GPLv2+ license and includes | |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
7 | */ | |
8 | ||
f7f3304a | 9 | #include "squid.h" |
d4806c91 | 10 | #include "AccessLogEntry.h" |
12daeef6 | 11 | #include "auth/digest/Config.h" |
aa110616 | 12 | #include "auth/digest/User.h" |
616cfc4c | 13 | #include "auth/digest/UserRequest.h" |
928f3421 | 14 | #include "auth/State.h" |
86c63190 | 15 | #include "format/Format.h" |
24438ec5 AJ |
16 | #include "helper.h" |
17 | #include "helper/Reply.h" | |
a5bac1d2 | 18 | #include "HttpHeaderTools.h" |
928f3421 AJ |
19 | #include "HttpReply.h" |
20 | #include "HttpRequest.h" | |
d4806c91 | 21 | #include "MemBuf.h" |
928f3421 AJ |
22 | #include "SquidTime.h" |
23 | ||
c7baff40 | 24 | Auth::Digest::UserRequest::UserRequest() : |
b20ce974 | 25 | noncehex(NULL), |
f53969cc SM |
26 | cnonce(NULL), |
27 | realm(NULL), | |
28 | pszPass(NULL), | |
29 | algorithm(NULL), | |
30 | pszMethod(NULL), | |
31 | qop(NULL), | |
32 | uri(NULL), | |
33 | response(NULL), | |
34 | nonce(NULL) | |
1032a194 AJ |
35 | { |
36 | memset(nc, 0, sizeof(nc)); | |
37 | memset(&flags, 0, sizeof(flags)); | |
38 | } | |
928f3421 AJ |
39 | |
40 | /** | |
41 | * Delete the digest request structure. | |
42 | * Does NOT delete related AuthUser structures | |
43 | */ | |
c7baff40 | 44 | Auth::Digest::UserRequest::~UserRequest() |
928f3421 | 45 | { |
8bf217bd | 46 | assert(LockCount()==0); |
ea0695f2 | 47 | |
b20ce974 | 48 | safe_free(noncehex); |
928f3421 AJ |
49 | safe_free(cnonce); |
50 | safe_free(realm); | |
51 | safe_free(pszPass); | |
52 | safe_free(algorithm); | |
53 | safe_free(pszMethod); | |
54 | safe_free(qop); | |
55 | safe_free(uri); | |
56 | safe_free(response); | |
57 | ||
58 | if (nonce) | |
59 | authDigestNonceUnlink(nonce); | |
60 | } | |
61 | ||
928f3421 | 62 | int |
c7baff40 | 63 | Auth::Digest::UserRequest::authenticated() const |
928f3421 | 64 | { |
d87154ee | 65 | if (user() != NULL && user()->credentials() == Auth::Ok) |
928f3421 AJ |
66 | return 1; |
67 | ||
68 | return 0; | |
69 | } | |
70 | ||
d4806c91 CT |
71 | const char * |
72 | Auth::Digest::UserRequest::credentialsStr() | |
73 | { | |
74 | return realm; | |
75 | } | |
76 | ||
928f3421 AJ |
77 | /** log a digest user in |
78 | */ | |
79 | void | |
789217a2 | 80 | Auth::Digest::UserRequest::authenticate(HttpRequest * request, ConnStateData *, Http::HdrType) |
928f3421 | 81 | { |
928f3421 AJ |
82 | HASHHEX SESSIONKEY; |
83 | HASHHEX HA2 = ""; | |
84 | HASHHEX Response; | |
85 | ||
928f3421 | 86 | /* if the check has corrupted the user, just return */ |
d87154ee | 87 | if (user() == NULL || user()->credentials() == Auth::Failed) { |
928f3421 AJ |
88 | return; |
89 | } | |
90 | ||
d87154ee | 91 | Auth::User::Pointer auth_user = user(); |
928f3421 | 92 | |
aa110616 | 93 | Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User*>(auth_user.getRaw()); |
56a49fda | 94 | assert(digest_user != NULL); |
928f3421 | 95 | |
c7baff40 | 96 | Auth::Digest::UserRequest *digest_request = this; |
56a49fda AJ |
97 | |
98 | /* do we have the HA1 */ | |
928f3421 | 99 | if (!digest_user->HA1created) { |
d87154ee | 100 | auth_user->credentials(Auth::Pending); |
928f3421 AJ |
101 | return; |
102 | } | |
103 | ||
104 | if (digest_request->nonce == NULL) { | |
105 | /* this isn't a nonce we issued */ | |
d87154ee | 106 | auth_user->credentials(Auth::Failed); |
928f3421 AJ |
107 | return; |
108 | } | |
109 | ||
110 | DigestCalcHA1(digest_request->algorithm, NULL, NULL, NULL, | |
b20ce974 | 111 | authenticateDigestNonceNonceHex(digest_request->nonce), |
928f3421 AJ |
112 | digest_request->cnonce, |
113 | digest_user->HA1, SESSIONKEY); | |
7f06a3d8 | 114 | SBuf sTmp = request->method.image(); |
b20ce974 | 115 | DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceHex(digest_request->nonce), |
928f3421 | 116 | digest_request->nc, digest_request->cnonce, digest_request->qop, |
7f06a3d8 | 117 | sTmp.c_str(), digest_request->uri, HA2, Response); |
928f3421 AJ |
118 | |
119 | debugs(29, 9, "\nResponse = '" << digest_request->response << "'\nsquid is = '" << Response << "'"); | |
120 | ||
121 | if (strcasecmp(digest_request->response, Response) != 0) { | |
122 | if (!digest_request->flags.helper_queried) { | |
123 | /* Query the helper in case the password has changed */ | |
3dd52a0b | 124 | digest_request->flags.helper_queried = true; |
d87154ee | 125 | auth_user->credentials(Auth::Pending); |
928f3421 AJ |
126 | return; |
127 | } | |
128 | ||
dc79fed8 | 129 | if (static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->PostWorkaround && request->method != Http::METHOD_GET) { |
928f3421 AJ |
130 | /* Ugly workaround for certain very broken browsers using the |
131 | * wrong method to calculate the request-digest on POST request. | |
132 | * This should be deleted once Digest authentication becomes more | |
133 | * widespread and such broken browsers no longer are commonly | |
134 | * used. | |
135 | */ | |
7f06a3d8 | 136 | sTmp = HttpRequestMethod(Http::METHOD_GET).image(); |
b20ce974 | 137 | DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceHex(digest_request->nonce), |
928f3421 | 138 | digest_request->nc, digest_request->cnonce, digest_request->qop, |
7f06a3d8 | 139 | sTmp.c_str(), digest_request->uri, HA2, Response); |
928f3421 AJ |
140 | |
141 | if (strcasecmp(digest_request->response, Response)) { | |
d87154ee | 142 | auth_user->credentials(Auth::Failed); |
3dd52a0b | 143 | digest_request->flags.invalid_password = true; |
928f3421 AJ |
144 | digest_request->setDenyMessage("Incorrect password"); |
145 | return; | |
146 | } else { | |
789217a2 | 147 | const char *useragent = request->header.getStr(Http::HdrType::USER_AGENT); |
928f3421 | 148 | |
08acdd08 | 149 | static Ip::Address last_broken_addr; |
928f3421 AJ |
150 | static int seen_broken_client = 0; |
151 | ||
152 | if (!seen_broken_client) { | |
4dd643d5 | 153 | last_broken_addr.setNoAddr(); |
928f3421 AJ |
154 | seen_broken_client = 1; |
155 | } | |
156 | ||
157 | if (last_broken_addr != request->client_addr) { | |
c7baff40 | 158 | debugs(29, DBG_IMPORTANT, "Digest POST bug detected from " << |
928f3421 AJ |
159 | request->client_addr << " using '" << |
160 | (useragent ? useragent : "-") << | |
161 | "'. Please upgrade browser. See Bug #630 for details."); | |
162 | ||
163 | last_broken_addr = request->client_addr; | |
164 | } | |
165 | } | |
166 | } else { | |
d87154ee | 167 | auth_user->credentials(Auth::Failed); |
3dd52a0b | 168 | digest_request->flags.invalid_password = true; |
928f3421 AJ |
169 | digest_request->setDenyMessage("Incorrect password"); |
170 | return; | |
171 | } | |
c9dbe80f | 172 | } |
928f3421 | 173 | |
c9dbe80f | 174 | /* check for stale nonce */ |
6b634dc3 FB |
175 | /* check Auth::Pending to avoid loop */ |
176 | ||
177 | if (!authDigestNonceIsValid(digest_request->nonce, digest_request->nc) && user()->credentials() != Auth::Pending) { | |
b20ce974 | 178 | debugs(29, 3, auth_user->username() << "' validated OK but nonce stale: " << digest_request->noncehex); |
6b634dc3 FB |
179 | /* Pending prevent banner and makes a ldap control */ |
180 | auth_user->credentials(Auth::Pending); | |
181 | nonce->flags.valid = false; | |
182 | authDigestNoncePurge(nonce); | |
c9dbe80f | 183 | return; |
928f3421 AJ |
184 | } |
185 | ||
d87154ee | 186 | auth_user->credentials(Auth::Ok); |
928f3421 AJ |
187 | |
188 | /* password was checked and did match */ | |
65cbd5a7 | 189 | debugs(29, 4, "user '" << auth_user->username() << "' validated OK"); |
928f3421 AJ |
190 | } |
191 | ||
51a3dd58 | 192 | Auth::Direction |
c7baff40 | 193 | Auth::Digest::UserRequest::module_direction() |
928f3421 | 194 | { |
616cfc4c | 195 | if (user()->auth_type != Auth::AUTH_DIGEST) |
51a3dd58 | 196 | return Auth::CRED_ERROR; |
928f3421 | 197 | |
d232141d | 198 | switch (user()->credentials()) { |
928f3421 | 199 | |
d87154ee | 200 | case Auth::Ok: |
51a3dd58 | 201 | return Auth::CRED_VALID; |
928f3421 | 202 | |
572d2e31 | 203 | case Auth::Handshake: |
d87154ee | 204 | case Auth::Failed: |
928f3421 | 205 | /* send new challenge */ |
51a3dd58 | 206 | return Auth::CRED_CHALLENGE; |
d232141d | 207 | |
d87154ee AJ |
208 | case Auth::Unchecked: |
209 | case Auth::Pending: | |
51a3dd58 | 210 | return Auth::CRED_LOOKUP; |
d232141d AJ |
211 | |
212 | default: | |
51a3dd58 | 213 | return Auth::CRED_ERROR; |
928f3421 | 214 | } |
928f3421 AJ |
215 | } |
216 | ||
928f3421 | 217 | void |
c7baff40 | 218 | Auth::Digest::UserRequest::addAuthenticationInfoHeader(HttpReply * rep, int accel) |
928f3421 | 219 | { |
789217a2 | 220 | Http::HdrType type; |
928f3421 AJ |
221 | |
222 | /* don't add to authentication error pages */ | |
9b769c67 AJ |
223 | if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired) |
224 | || (accel && rep->sline.status() == Http::scUnauthorized)) | |
928f3421 AJ |
225 | return; |
226 | ||
789217a2 | 227 | type = accel ? Http::HdrType::AUTHENTICATION_INFO : Http::HdrType::PROXY_AUTHENTICATION_INFO; |
928f3421 AJ |
228 | |
229 | #if WAITING_FOR_TE | |
230 | /* test for http/1.1 transfer chunked encoding */ | |
231 | if (chunkedtest) | |
232 | return; | |
233 | #endif | |
234 | ||
dc79fed8 | 235 | if ((static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram) && authDigestNonceLastRequest(nonce)) { |
3dd52a0b | 236 | flags.authinfo_sent = true; |
572d2e31 | 237 | Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(user().getRaw()); |
3d01c5ab AJ |
238 | if (!digest_user) |
239 | return; | |
240 | ||
572d2e31 HN |
241 | digest_nonce_h *nextnonce = digest_user->currentNonce(); |
242 | if (!nextnonce || authDigestNonceLastRequest(nonce)) { | |
243 | nextnonce = authenticateDigestNonceNew(); | |
244 | authDigestUserLinkNonce(digest_user, nextnonce); | |
245 | } | |
b20ce974 | 246 | debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nextnonce) << "\""); |
247 | httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nextnonce)); | |
928f3421 AJ |
248 | } |
249 | } | |
250 | ||
251 | #if WAITING_FOR_TE | |
928f3421 | 252 | void |
c7baff40 | 253 | Auth::Digest::UserRequest::addAuthenticationInfoTrailer(HttpReply * rep, int accel) |
928f3421 AJ |
254 | { |
255 | int type; | |
256 | ||
257 | if (!auth_user_request) | |
258 | return; | |
259 | ||
260 | /* has the header already been send? */ | |
261 | if (flags.authinfo_sent) | |
262 | return; | |
263 | ||
264 | /* don't add to authentication error pages */ | |
9b769c67 AJ |
265 | if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired) |
266 | || (accel && rep->sline.status() == Http::scUnauthorized)) | |
928f3421 AJ |
267 | return; |
268 | ||
789217a2 | 269 | type = accel ? Http::HdrType::AUTHENTICATION_INFO : Http::HdrType::PROXY_AUTHENTICATION_INFO; |
928f3421 | 270 | |
372fccd6 | 271 | if ((static_cast<Auth::Digest::Config*>(digestScheme::GetInstance()->getConfig())->authenticate) && authDigestNonceLastRequest(nonce)) { |
572d2e31 HN |
272 | Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw()); |
273 | nonce = digest_user->currentNonce(); | |
274 | if (!nonce) { | |
275 | nonce = authenticateDigestNonceNew(); | |
276 | authDigestUserLinkNonce(digest_user, nonce); | |
277 | } | |
b20ce974 | 278 | debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nonce) << "\""); |
279 | httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nonce)); | |
928f3421 AJ |
280 | } |
281 | } | |
282 | #endif | |
283 | ||
284 | /* send the initial data to a digest authenticator module */ | |
285 | void | |
30c3f584 | 286 | Auth::Digest::UserRequest::startHelperLookup(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data) |
928f3421 | 287 | { |
928f3421 | 288 | char buf[8192]; |
56a49fda | 289 | |
616cfc4c | 290 | assert(user() != NULL && user()->auth_type == Auth::AUTH_DIGEST); |
c7baff40 | 291 | debugs(29, 9, HERE << "'\"" << user()->username() << "\":\"" << realm << "\"'"); |
928f3421 | 292 | |
dc79fed8 | 293 | if (static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram == NULL) { |
d232141d | 294 | debugs(29, DBG_CRITICAL, "ERROR: No Digest authentication program configured."); |
4c535e87 | 295 | handler(data); |
928f3421 AJ |
296 | return; |
297 | } | |
298 | ||
d4806c91 | 299 | const char *keyExtras = helperRequestKeyExtras(request, al); |
7e851a3e SK |
300 | if (keyExtras) |
301 | snprintf(buf, 8192, "\"%s\":\"%s\" %s\n", user()->username(), realm, keyExtras); | |
302 | else | |
303 | snprintf(buf, 8192, "\"%s\":\"%s\"\n", user()->username(), realm); | |
928f3421 | 304 | |
c7baff40 | 305 | helperSubmit(digestauthenticators, buf, Auth::Digest::UserRequest::HandleReply, |
1c756645 | 306 | new Auth::StateData(this, handler, data)); |
928f3421 AJ |
307 | } |
308 | ||
309 | void | |
24438ec5 | 310 | Auth::Digest::UserRequest::HandleReply(void *data, const Helper::Reply &reply) |
928f3421 | 311 | { |
1c756645 | 312 | Auth::StateData *replyData = static_cast<Auth::StateData *>(data); |
e166785a | 313 | debugs(29, 9, HERE << "reply=" << reply); |
928f3421 AJ |
314 | |
315 | assert(replyData->auth_user_request != NULL); | |
c7baff40 | 316 | Auth::UserRequest::Pointer auth_user_request = replyData->auth_user_request; |
928f3421 | 317 | |
71e7400c AJ |
318 | // add new helper kv-pair notes to the credentials object |
319 | // so that any transaction using those credentials can access them | |
d665de37 A |
320 | static const NotePairs::Names appendables = { SBuf("group"), SBuf("nonce"), SBuf("tag") }; |
321 | auth_user_request->user()->notes.replaceOrAddOrAppend(&reply.notes, appendables); | |
c10ebce8 AJ |
322 | // remove any private credentials detail which got added. |
323 | auth_user_request->user()->notes.remove("ha1"); | |
71e7400c | 324 | |
c69199bb | 325 | static bool oldHelperWarningDone = false; |
dacb64b9 | 326 | switch (reply.result) { |
2428ce02 | 327 | case Helper::Unknown: { |
c69199bb AJ |
328 | // Squid 3.3 and older the digest helper only returns a HA1 hash (no "OK") |
329 | // the HA1 will be found in content() for these responses. | |
330 | if (!oldHelperWarningDone) { | |
331 | debugs(29, DBG_IMPORTANT, "WARNING: Digest auth helper returned old format HA1 response. It needs to be upgraded."); | |
332 | oldHelperWarningDone=true; | |
333 | } | |
56a49fda | 334 | |
c69199bb AJ |
335 | /* allow this because the digest_request pointer is purely local */ |
336 | Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw()); | |
337 | assert(digest_user != NULL); | |
928f3421 | 338 | |
c69199bb AJ |
339 | CvtBin(reply.other().content(), digest_user->HA1); |
340 | digest_user->HA1created = 1; | |
e166785a | 341 | } |
dacb64b9 | 342 | break; |
e166785a | 343 | |
2428ce02 | 344 | case Helper::Okay: { |
56a49fda | 345 | /* allow this because the digest_request pointer is purely local */ |
aa110616 | 346 | Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw()); |
56a49fda AJ |
347 | assert(digest_user != NULL); |
348 | ||
75d47340 | 349 | if (const char *ha1Note = reply.notes.findFirst("ha1")) { |
cf9f0261 | 350 | CvtBin(ha1Note, digest_user->HA1); |
c69199bb AJ |
351 | digest_user->HA1created = 1; |
352 | } else { | |
353 | debugs(29, DBG_IMPORTANT, "ERROR: Digest auth helper did not produce a HA1. Using the wrong helper program? received: " << reply); | |
354 | } | |
928f3421 | 355 | } |
dacb64b9 | 356 | break; |
e166785a | 357 | |
2428ce02 | 358 | case Helper::TT: |
c69199bb | 359 | debugs(29, DBG_IMPORTANT, "ERROR: Digest auth does not support the result code received. Using the wrong helper program? received: " << reply); |
f53969cc | 360 | // fall through to next case. Handle this as an ERR response. |
c69199bb | 361 | |
32fd6d8a | 362 | case Helper::TimedOut: |
2428ce02 | 363 | case Helper::BrokenHelper: |
f53969cc SM |
364 | // TODO retry the broken lookup on another helper? |
365 | // fall through to next case for now. Handle this as an ERR response silently. | |
2428ce02 | 366 | case Helper::Error: { |
c69199bb AJ |
367 | /* allow this because the digest_request pointer is purely local */ |
368 | Auth::Digest::UserRequest *digest_request = dynamic_cast<Auth::Digest::UserRequest *>(auth_user_request.getRaw()); | |
369 | assert(digest_request); | |
370 | ||
371 | digest_request->user()->credentials(Auth::Failed); | |
3dd52a0b | 372 | digest_request->flags.invalid_password = true; |
c69199bb | 373 | |
75d47340 CT |
374 | SBuf msgNote; |
375 | if (reply.notes.find(msgNote, "message")) { | |
376 | digest_request->setDenyMessage(msgNote.c_str()); | |
c69199bb AJ |
377 | } else if (reply.other().hasContent()) { |
378 | // old helpers did send ERR result but a bare message string instead of message= key name. | |
379 | digest_request->setDenyMessage(reply.other().content()); | |
380 | if (!oldHelperWarningDone) { | |
381 | debugs(29, DBG_IMPORTANT, "WARNING: Digest auth helper returned old format ERR response. It needs to be upgraded."); | |
382 | oldHelperWarningDone=true; | |
383 | } | |
384 | } | |
385 | } | |
386 | break; | |
e166785a | 387 | } |
928f3421 | 388 | |
e166785a | 389 | void *cbdata = NULL; |
928f3421 | 390 | if (cbdataReferenceValidDone(replyData->data, &cbdata)) |
4c535e87 | 391 | replyData->handler(cbdata); |
928f3421 | 392 | |
1c756645 | 393 | delete replyData; |
928f3421 | 394 | } |
f53969cc | 395 |