]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/auth/digest/UserRequest.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / auth / digest / UserRequest.cc
index dfd72462aa991454a0592d56e08c329420fbef08..141535d15332a6fcad728e7a66dfe728b2d96353 100644 (file)
@@ -1,26 +1,41 @@
+/*
+ * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
 #include "squid.h"
-#include "auth/digest/auth_digest.h"
+#include "AccessLogEntry.h"
+#include "auth/digest/Config.h"
 #include "auth/digest/User.h"
 #include "auth/digest/UserRequest.h"
 #include "auth/State.h"
-#include "charset.h"
+#include "format/Format.h"
+#include "helper.h"
+#include "helper/Reply.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
+#include "MemBuf.h"
 #include "SquidTime.h"
 
 Auth::Digest::UserRequest::UserRequest() :
-        nonceb64(NULL),
-        cnonce(NULL),
-        realm(NULL),
-        pszPass(NULL),
-        algorithm(NULL),
-        pszMethod(NULL),
-        qop(NULL),
-        uri(NULL),
-        response(NULL),
-        nonce(NULL)
-{}
+    noncehex(NULL),
+    cnonce(NULL),
+    realm(NULL),
+    pszPass(NULL),
+    algorithm(NULL),
+    pszMethod(NULL),
+    qop(NULL),
+    uri(NULL),
+    response(NULL),
+    nonce(NULL)
+{
+    memset(nc, 0, sizeof(nc));
+    memset(&flags, 0, sizeof(flags));
+}
 
 /**
  * Delete the digest request structure.
@@ -28,9 +43,9 @@ Auth::Digest::UserRequest::UserRequest() :
  */
 Auth::Digest::UserRequest::~UserRequest()
 {
-    assert(RefCountCount()==0);
+    assert(LockCount()==0);
 
-    safe_free(nonceb64);
+    safe_free(noncehex);
     safe_free(cnonce);
     safe_free(realm);
     safe_free(pszPass);
@@ -53,10 +68,16 @@ Auth::Digest::UserRequest::authenticated() const
     return 0;
 }
 
+const char *
+Auth::Digest::UserRequest::credentialsStr()
+{
+    return realm;
+}
+
 /** log a digest user in
  */
 void
-Auth::Digest::UserRequest::authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type)
+Auth::Digest::UserRequest::authenticate(HttpRequest * request, ConnStateData *, Http::HdrType)
 {
     HASHHEX SESSIONKEY;
     HASHHEX HA2 = "";
@@ -87,47 +108,49 @@ Auth::Digest::UserRequest::authenticate(HttpRequest * request, ConnStateData * c
     }
 
     DigestCalcHA1(digest_request->algorithm, NULL, NULL, NULL,
-                  authenticateDigestNonceNonceb64(digest_request->nonce),
+                  authenticateDigestNonceNonceHex(digest_request->nonce),
                   digest_request->cnonce,
                   digest_user->HA1, SESSIONKEY);
-    DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce),
+    SBuf sTmp = request->method.image();
+    DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceHex(digest_request->nonce),
                        digest_request->nc, digest_request->cnonce, digest_request->qop,
-                       RequestMethodStr(request->method), digest_request->uri, HA2, Response);
+                       sTmp.c_str(), digest_request->uri, HA2, Response);
 
     debugs(29, 9, "\nResponse = '" << digest_request->response << "'\nsquid is = '" << Response << "'");
 
     if (strcasecmp(digest_request->response, Response) != 0) {
         if (!digest_request->flags.helper_queried) {
             /* Query the helper in case the password has changed */
-            digest_request->flags.helper_queried = 1;
+            digest_request->flags.helper_queried = true;
             auth_user->credentials(Auth::Pending);
             return;
         }
 
-        if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->PostWorkaround && request->method != Http::METHOD_GET) {
+        if (static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->PostWorkaround && request->method != Http::METHOD_GET) {
             /* Ugly workaround for certain very broken browsers using the
              * wrong method to calculate the request-digest on POST request.
              * This should be deleted once Digest authentication becomes more
              * widespread and such broken browsers no longer are commonly
              * used.
              */
-            DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce),
+            sTmp = HttpRequestMethod(Http::METHOD_GET).image();
+            DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceHex(digest_request->nonce),
                                digest_request->nc, digest_request->cnonce, digest_request->qop,
-                               RequestMethodStr(Http::METHOD_GET), digest_request->uri, HA2, Response);
+                               sTmp.c_str(), digest_request->uri, HA2, Response);
 
             if (strcasecmp(digest_request->response, Response)) {
                 auth_user->credentials(Auth::Failed);
-                digest_request->flags.invalid_password = 1;
+                digest_request->flags.invalid_password = true;
                 digest_request->setDenyMessage("Incorrect password");
                 return;
             } else {
-                const char *useragent = request->header.getStr(HDR_USER_AGENT);
+                const char *useragent = request->header.getStr(Http::HdrType::USER_AGENT);
 
                 static Ip::Address last_broken_addr;
                 static int seen_broken_client = 0;
 
                 if (!seen_broken_client) {
-                    last_broken_addr.SetNoAddr();
+                    last_broken_addr.setNoAddr();
                     seen_broken_client = 1;
                 }
 
@@ -142,29 +165,28 @@ Auth::Digest::UserRequest::authenticate(HttpRequest * request, ConnStateData * c
             }
         } else {
             auth_user->credentials(Auth::Failed);
-            digest_request->flags.invalid_password = 1;
+            digest_request->flags.invalid_password = true;
             digest_request->setDenyMessage("Incorrect password");
             return;
         }
+    }
 
-        /* check for stale nonce */
-        if (!authDigestNonceIsValid(digest_request->nonce, digest_request->nc)) {
-            debugs(29, 3, HERE << "user '" << auth_user->username() << "' validated OK but nonce stale");
-            auth_user->credentials(Auth::Failed);
-            digest_request->setDenyMessage("Stale nonce");
-            return;
-        }
+    /* check for stale nonce */
+    /* check Auth::Pending to avoid loop */
+
+    if (!authDigestNonceIsValid(digest_request->nonce, digest_request->nc) && user()->credentials() != Auth::Pending) {
+        debugs(29, 3, auth_user->username() << "' validated OK but nonce stale: " << digest_request->noncehex);
+        /* Pending prevent banner and makes a ldap control */
+        auth_user->credentials(Auth::Pending);
+        nonce->flags.valid = false;
+        authDigestNoncePurge(nonce);
+        return;
     }
 
     auth_user->credentials(Auth::Ok);
 
     /* password was checked and did match */
-    debugs(29, 4, HERE << "user '" << auth_user->username() << "' validated OK");
-
-    /* auth_user is now linked, we reset these values
-     * after external auth occurs anyway */
-    auth_user->expiretime = current_time.tv_sec;
-    return;
+    debugs(29, 4, "user '" << auth_user->username() << "' validated OK");
 }
 
 Auth::Direction
@@ -178,6 +200,7 @@ Auth::Digest::UserRequest::module_direction()
     case Auth::Ok:
         return Auth::CRED_VALID;
 
+    case Auth::Handshake:
     case Auth::Failed:
         /* send new challenge */
         return Auth::CRED_CHALLENGE;
@@ -194,15 +217,14 @@ Auth::Digest::UserRequest::module_direction()
 void
 Auth::Digest::UserRequest::addAuthenticationInfoHeader(HttpReply * rep, int accel)
 {
-    http_hdr_type type;
+    Http::HdrType type;
 
     /* don't add to authentication error pages */
-
-    if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED)
-            || (accel && rep->sline.status == HTTP_UNAUTHORIZED))
+    if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
+            || (accel && rep->sline.status() == Http::scUnauthorized))
         return;
 
-    type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
+    type = accel ? Http::HdrType::AUTHENTICATION_INFO : Http::HdrType::PROXY_AUTHENTICATION_INFO;
 
 #if WAITING_FOR_TE
     /* test for http/1.1 transfer chunked encoding */
@@ -210,10 +232,19 @@ Auth::Digest::UserRequest::addAuthenticationInfoHeader(HttpReply * rep, int acce
         return;
 #endif
 
-    if ((static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->authenticateProgram) && authDigestNonceLastRequest(nonce)) {
-        flags.authinfo_sent = 1;
-        debugs(29, 9, HERE << "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce) << "\"");
-        httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce));
+    if ((static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram) && authDigestNonceLastRequest(nonce)) {
+        flags.authinfo_sent = true;
+        Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(user().getRaw());
+        if (!digest_user)
+            return;
+
+        digest_nonce_h *nextnonce = digest_user->currentNonce();
+        if (!nextnonce || authDigestNonceLastRequest(nonce)) {
+            nextnonce = authenticateDigestNonceNew();
+            authDigestUserLinkNonce(digest_user, nextnonce);
+        }
+        debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nextnonce) << "\"");
+        httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nextnonce));
     }
 }
 
@@ -231,88 +262,134 @@ Auth::Digest::UserRequest::addAuthenticationInfoTrailer(HttpReply * rep, int acc
         return;
 
     /* don't add to authentication error pages */
-    if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED)
-            || (accel && rep->sline.status == HTTP_UNAUTHORIZED))
+    if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
+            || (accel && rep->sline.status() == Http::scUnauthorized))
         return;
 
-    type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
+    type = accel ? Http::HdrType::AUTHENTICATION_INFO : Http::HdrType::PROXY_AUTHENTICATION_INFO;
 
     if ((static_cast<Auth::Digest::Config*>(digestScheme::GetInstance()->getConfig())->authenticate) && authDigestNonceLastRequest(nonce)) {
-        debugs(29, 9, HERE << "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce) << "\"");
-        httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce));
+        Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
+        nonce = digest_user->currentNonce();
+        if (!nonce) {
+            nonce = authenticateDigestNonceNew();
+            authDigestUserLinkNonce(digest_user, nonce);
+        }
+        debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nonce) << "\"");
+        httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nonce));
     }
 }
 #endif
 
 /* send the initial data to a digest authenticator module */
 void
-Auth::Digest::UserRequest::module_start(AUTHCB * handler, void *data)
+Auth::Digest::UserRequest::startHelperLookup(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data)
 {
     char buf[8192];
 
     assert(user() != NULL && user()->auth_type == Auth::AUTH_DIGEST);
     debugs(29, 9, HERE << "'\"" << user()->username() << "\":\"" << realm << "\"'");
 
-    if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->authenticateProgram == NULL) {
+    if (static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram == NULL) {
         debugs(29, DBG_CRITICAL, "ERROR: No Digest authentication program configured.");
         handler(data);
         return;
     }
 
-    if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->utf8) {
-        char userstr[1024];
-        latin1_to_utf8(userstr, sizeof(userstr), user()->username());
-        snprintf(buf, 8192, "\"%s\":\"%s\"\n", userstr, realm);
-    } else {
+    const char *keyExtras = helperRequestKeyExtras(request, al);
+    if (keyExtras)
+        snprintf(buf, 8192, "\"%s\":\"%s\" %s\n", user()->username(), realm, keyExtras);
+    else
         snprintf(buf, 8192, "\"%s\":\"%s\"\n", user()->username(), realm);
-    }
 
     helperSubmit(digestauthenticators, buf, Auth::Digest::UserRequest::HandleReply,
                  new Auth::StateData(this, handler, data));
 }
 
 void
-Auth::Digest::UserRequest::HandleReply(void *data, char *reply)
+Auth::Digest::UserRequest::HandleReply(void *data, const Helper::Reply &reply)
 {
     Auth::StateData *replyData = static_cast<Auth::StateData *>(data);
-    char *t = NULL;
-    void *cbdata;
-    debugs(29, 9, HERE << "{" << (reply ? reply : "<NULL>") << "}");
-
-    if (reply) {
-        if ((t = strchr(reply, ' '))) {
-            *t = '\0';
-            ++t;
-        }
-
-        if (*reply == '\0' || *reply == '\n')
-            reply = NULL;
-    }
+    debugs(29, 9, HERE << "reply=" << reply);
 
     assert(replyData->auth_user_request != NULL);
     Auth::UserRequest::Pointer auth_user_request = replyData->auth_user_request;
 
-    if (reply && (strncasecmp(reply, "ERR", 3) == 0)) {
+    // add new helper kv-pair notes to the credentials object
+    // so that any transaction using those credentials can access them
+    static const NotePairs::Names appendables = { SBuf("group"), SBuf("nonce"), SBuf("tag") };
+    auth_user_request->user()->notes.replaceOrAddOrAppend(&reply.notes, appendables);
+    // remove any private credentials detail which got added.
+    auth_user_request->user()->notes.remove("ha1");
+
+    static bool oldHelperWarningDone = false;
+    switch (reply.result) {
+    case Helper::Unknown: {
+        // Squid 3.3 and older the digest helper only returns a HA1 hash (no "OK")
+        // the HA1 will be found in content() for these responses.
+        if (!oldHelperWarningDone) {
+            debugs(29, DBG_IMPORTANT, "WARNING: Digest auth helper returned old format HA1 response. It needs to be upgraded.");
+            oldHelperWarningDone=true;
+        }
+
         /* allow this because the digest_request pointer is purely local */
-        Auth::Digest::UserRequest *digest_request = dynamic_cast<Auth::Digest::UserRequest *>(auth_user_request.getRaw());
-        assert(digest_request);
+        Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
+        assert(digest_user != NULL);
 
-        digest_request->user()->credentials(Auth::Failed);
-        digest_request->flags.invalid_password = 1;
+        CvtBin(reply.other().content(), digest_user->HA1);
+        digest_user->HA1created = 1;
+    }
+    break;
 
-        if (t && *t)
-            digest_request->setDenyMessage(t);
-    } else if (reply) {
+    case Helper::Okay: {
         /* allow this because the digest_request pointer is purely local */
         Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
         assert(digest_user != NULL);
 
-        CvtBin(reply, digest_user->HA1);
-        digest_user->HA1created = 1;
+        if (const char *ha1Note = reply.notes.findFirst("ha1")) {
+            CvtBin(ha1Note, digest_user->HA1);
+            digest_user->HA1created = 1;
+        } else {
+            debugs(29, DBG_IMPORTANT, "ERROR: Digest auth helper did not produce a HA1. Using the wrong helper program? received: " << reply);
+        }
     }
+    break;
 
+    case Helper::TT:
+        debugs(29, DBG_IMPORTANT, "ERROR: Digest auth does not support the result code received. Using the wrong helper program? received: " << reply);
+    // fall through to next case. Handle this as an ERR response.
+
+    case Helper::TimedOut:
+    case Helper::BrokenHelper:
+    // TODO retry the broken lookup on another helper?
+    // fall through to next case for now. Handle this as an ERR response silently.
+    case Helper::Error: {
+        /* allow this because the digest_request pointer is purely local */
+        Auth::Digest::UserRequest *digest_request = dynamic_cast<Auth::Digest::UserRequest *>(auth_user_request.getRaw());
+        assert(digest_request);
+
+        digest_request->user()->credentials(Auth::Failed);
+        digest_request->flags.invalid_password = true;
+
+        SBuf msgNote;
+        if (reply.notes.find(msgNote, "message")) {
+            digest_request->setDenyMessage(msgNote.c_str());
+        } else if (reply.other().hasContent()) {
+            // old helpers did send ERR result but a bare message string instead of message= key name.
+            digest_request->setDenyMessage(reply.other().content());
+            if (!oldHelperWarningDone) {
+                debugs(29, DBG_IMPORTANT, "WARNING: Digest auth helper returned old format ERR response. It needs to be upgraded.");
+                oldHelperWarningDone=true;
+            }
+        }
+    }
+    break;
+    }
+
+    void *cbdata = NULL;
     if (cbdataReferenceValidDone(replyData->data, &cbdata))
         replyData->handler(cbdata);
 
     delete replyData;
 }
+