source: squid-ssl/trunk/fuentes/src/auth/digest/UserRequest.cc @ 5496

Last change on this file since 5496 was 5495, checked in by Juanma, 2 years ago

Initial release

File size: 14.2 KB
Line 
1/*
2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
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
9#include "squid.h"
10#include "AccessLogEntry.h"
11#include "auth/digest/Config.h"
12#include "auth/digest/User.h"
13#include "auth/digest/UserRequest.h"
14#include "auth/State.h"
15#include "charset.h"
16#include "format/Format.h"
17#include "helper.h"
18#include "helper/Reply.h"
19#include "HttpHeaderTools.h"
20#include "HttpReply.h"
21#include "HttpRequest.h"
22#include "MemBuf.h"
23#include "SquidTime.h"
24
25Auth::Digest::UserRequest::UserRequest() :
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)
36{
37    memset(nc, 0, sizeof(nc));
38    memset(&flags, 0, sizeof(flags));
39}
40
41/**
42 * Delete the digest request structure.
43 * Does NOT delete related AuthUser structures
44 */
45Auth::Digest::UserRequest::~UserRequest()
46{
47    assert(LockCount()==0);
48
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
63int
64Auth::Digest::UserRequest::authenticated() const
65{
66    if (user() != NULL && user()->credentials() == Auth::Ok)
67        return 1;
68
69    return 0;
70}
71
72const char *
73Auth::Digest::UserRequest::credentialsStr()
74{
75    return realm;
76}
77
78/** log a digest user in
79 */
80void
81Auth::Digest::UserRequest::authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type)
82{
83    HASHHEX SESSIONKEY;
84    HASHHEX HA2 = "";
85    HASHHEX Response;
86
87    /* if the check has corrupted the user, just return */
88    if (user() == NULL || user()->credentials() == Auth::Failed) {
89        return;
90    }
91
92    Auth::User::Pointer auth_user = user();
93
94    Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User*>(auth_user.getRaw());
95    assert(digest_user != NULL);
96
97    Auth::Digest::UserRequest *digest_request = this;
98
99    /* do we have the HA1 */
100    if (!digest_user->HA1created) {
101        auth_user->credentials(Auth::Pending);
102        return;
103    }
104
105    if (digest_request->nonce == NULL) {
106        /* this isn't a nonce we issued */
107        auth_user->credentials(Auth::Failed);
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);
115    SBuf sTmp = request->method.image();
116    DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce),
117                       digest_request->nc, digest_request->cnonce, digest_request->qop,
118                       sTmp.c_str(), digest_request->uri, HA2, Response);
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 */
125            digest_request->flags.helper_queried = true;
126            auth_user->credentials(Auth::Pending);
127            return;
128        }
129
130        if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->PostWorkaround && request->method != Http::METHOD_GET) {
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             */
137            sTmp = HttpRequestMethod(Http::METHOD_GET).image();
138            DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce),
139                               digest_request->nc, digest_request->cnonce, digest_request->qop,
140                               sTmp.c_str(), digest_request->uri, HA2, Response);
141
142            if (strcasecmp(digest_request->response, Response)) {
143                auth_user->credentials(Auth::Failed);
144                digest_request->flags.invalid_password = true;
145                digest_request->setDenyMessage("Incorrect password");
146                return;
147            } else {
148                const char *useragent = request->header.getStr(HDR_USER_AGENT);
149
150                static Ip::Address last_broken_addr;
151                static int seen_broken_client = 0;
152
153                if (!seen_broken_client) {
154                    last_broken_addr.setNoAddr();
155                    seen_broken_client = 1;
156                }
157
158                if (last_broken_addr != request->client_addr) {
159                    debugs(29, DBG_IMPORTANT, "Digest POST bug detected from " <<
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 {
168            auth_user->credentials(Auth::Failed);
169            digest_request->flags.invalid_password = true;
170            digest_request->setDenyMessage("Incorrect password");
171            return;
172        }
173    }
174
175    /* check for stale nonce */
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);
184        return;
185    }
186
187    auth_user->credentials(Auth::Ok);
188
189    /* password was checked and did match */
190    debugs(29, 4, HERE << "user '" << auth_user->username() << "' validated OK");
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
198Auth::Direction
199Auth::Digest::UserRequest::module_direction()
200{
201    if (user()->auth_type != Auth::AUTH_DIGEST)
202        return Auth::CRED_ERROR;
203
204    switch (user()->credentials()) {
205
206    case Auth::Ok:
207        return Auth::CRED_VALID;
208
209    case Auth::Handshake:
210    case Auth::Failed:
211        /* send new challenge */
212        return Auth::CRED_CHALLENGE;
213
214    case Auth::Unchecked:
215    case Auth::Pending:
216        return Auth::CRED_LOOKUP;
217
218    default:
219        return Auth::CRED_ERROR;
220    }
221}
222
223void
224Auth::Digest::UserRequest::addAuthenticationInfoHeader(HttpReply * rep, int accel)
225{
226    http_hdr_type type;
227
228    /* don't add to authentication error pages */
229    if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
230            || (accel && rep->sline.status() == Http::scUnauthorized))
231        return;
232
233    type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
234
235#if WAITING_FOR_TE
236    /* test for http/1.1 transfer chunked encoding */
237    if (chunkedtest)
238        return;
239#endif
240
241    if ((static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->authenticateProgram) && authDigestNonceLastRequest(nonce)) {
242        flags.authinfo_sent = true;
243        Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(user().getRaw());
244        if (!digest_user)
245            return;
246
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));
254    }
255}
256
257#if WAITING_FOR_TE
258void
259Auth::Digest::UserRequest::addAuthenticationInfoTrailer(HttpReply * rep, int accel)
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 */
271    if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
272            || (accel && rep->sline.status() == Http::scUnauthorized))
273        return;
274
275    type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
276
277    if ((static_cast<Auth::Digest::Config*>(digestScheme::GetInstance()->getConfig())->authenticate) && authDigestNonceLastRequest(nonce)) {
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) << "\"");
285        httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce));
286    }
287}
288#endif
289
290/* send the initial data to a digest authenticator module */
291void
292Auth::Digest::UserRequest::startHelperLookup(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data)
293{
294    char buf[8192];
295
296    assert(user() != NULL && user()->auth_type == Auth::AUTH_DIGEST);
297    debugs(29, 9, HERE << "'\"" << user()->username() << "\":\"" << realm << "\"'");
298
299    if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->authenticateProgram == NULL) {
300        debugs(29, DBG_CRITICAL, "ERROR: No Digest authentication program configured.");
301        handler(data);
302        return;
303    }
304
305    const char *keyExtras = helperRequestKeyExtras(request, al);
306    if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->utf8) {
307        char userstr[1024];
308        latin1_to_utf8(userstr, sizeof(userstr), user()->username());
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);
313    } else {
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);
318    }
319
320    helperSubmit(digestauthenticators, buf, Auth::Digest::UserRequest::HandleReply,
321                 new Auth::StateData(this, handler, data));
322}
323
324void
325Auth::Digest::UserRequest::HandleReply(void *data, const Helper::Reply &reply)
326{
327    Auth::StateData *replyData = static_cast<Auth::StateData *>(data);
328    debugs(29, 9, HERE << "reply=" << reply);
329
330    assert(replyData->auth_user_request != NULL);
331    Auth::UserRequest::Pointer auth_user_request = replyData->auth_user_request;
332
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);
336    // remove any private credentials detail which got added.
337    auth_user_request->user()->notes.remove("ha1");
338
339    static bool oldHelperWarningDone = false;
340    switch (reply.result) {
341    case Helper::Unknown: {
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        }
348
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);
352
353        CvtBin(reply.other().content(), digest_user->HA1);
354        digest_user->HA1created = 1;
355    }
356    break;
357
358    case Helper::Okay: {
359        /* allow this because the digest_request pointer is purely local */
360        Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
361        assert(digest_user != NULL);
362
363        const char *ha1Note = reply.notes.findFirst("ha1");
364        if (ha1Note != NULL) {
365            CvtBin(ha1Note, digest_user->HA1);
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        }
370    }
371    break;
372
373    case Helper::TT:
374        debugs(29, DBG_IMPORTANT, "ERROR: Digest auth does not support the result code received. Using the wrong helper program? received: " << reply);
375    // fall through to next case. Handle this as an ERR response.
376
377    case Helper::BrokenHelper:
378    // TODO retry the broken lookup on another helper?
379    // fall through to next case for now. Handle this as an ERR response silently.
380
381    case Helper::Error: {
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);
387        digest_request->flags.invalid_password = true;
388
389        const char *msgNote = reply.notes.find("message");
390        if (msgNote != NULL) {
391            digest_request->setDenyMessage(msgNote);
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;
402    }
403
404    void *cbdata = NULL;
405    if (cbdataReferenceValidDone(replyData->data, &cbdata))
406        replyData->handler(cbdata);
407
408    delete replyData;
409}
410
Note: See TracBrowser for help on using the repository browser.