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