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