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