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