]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/ssl/helper.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / ssl / helper.cc
index 511643239f07e6ca18824d23b1f74d1c52ff9f6d..054835cc22ce58ce7b39ec426eacaf2783d1cd2d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ * 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.
@@ -9,48 +9,84 @@
 #include "squid.h"
 #include "../helper.h"
 #include "anyp/PortCfg.h"
+#include "cache_cf.h"
+#include "fs_io.h"
 #include "helper/Reply.h"
+#include "Parsing.h"
+#include "sbuf/Stream.h"
 #include "SquidConfig.h"
 #include "SquidString.h"
 #include "SquidTime.h"
 #include "ssl/cert_validate_message.h"
 #include "ssl/Config.h"
 #include "ssl/helper.h"
-#include "SwapDir.h"
 #include "wordlist.h"
 
-LruMap<Ssl::CertValidationResponse> *Ssl::CertValidationHelper::HelperCache = NULL;
+#include <limits>
+
+Ssl::CertValidationHelper::CacheType *Ssl::CertValidationHelper::HelperCache = nullptr;
 
 #if USE_SSL_CRTD
-Ssl::Helper * Ssl::Helper::GetInstance()
-{
-    static Ssl::Helper sslHelper;
-    return &sslHelper;
-}
 
-Ssl::Helper::Helper() : ssl_crtd(NULL)
-{
-}
+namespace Ssl {
+
+/// Initiator of an Ssl::Helper query.
+class GeneratorRequestor {
+public:
+    GeneratorRequestor(HLPCB *aCallback, void *aData): callback(aCallback), data(aData) {}
+    HLPCB *callback;
+    CallbackData data;
+};
+
+/// A pending Ssl::Helper request, combining the original and collapsed queries.
+class GeneratorRequest {
+    CBDATA_CLASS(GeneratorRequest);
+
+public:
+    /// adds a GeneratorRequestor
+    void emplace(HLPCB *callback, void *data) { requestors.emplace_back(callback, data); }
+
+    SBuf query; ///< Ssl::Helper request message (GeneratorRequests key)
+
+    /// Ssl::Helper request initiators waiting for the same answer (FIFO).
+    typedef std::vector<GeneratorRequestor> GeneratorRequestors;
+    GeneratorRequestors requestors;
+};
+
+/// Ssl::Helper query:GeneratorRequest map
+typedef std::unordered_map<SBuf, GeneratorRequest*> GeneratorRequests;
+
+static void HandleGeneratorReply(void *data, const ::Helper::Reply &reply);
 
-Ssl::Helper::~Helper()
+} // namespace Ssl
+
+CBDATA_NAMESPACED_CLASS_INIT(Ssl, GeneratorRequest);
+
+/// prints Ssl::GeneratorRequest for debugging
+static std::ostream &
+operator <<(std::ostream &os, const Ssl::GeneratorRequest &gr)
 {
-    Shutdown();
+    return os << "crtGenRq" << gr.query.id.value << "/" << gr.requestors.size();
 }
 
+/// pending Ssl::Helper requests (to all certificate generator helpers combined)
+static Ssl::GeneratorRequests TheGeneratorRequests;
+
+helper *Ssl::Helper::ssl_crtd = nullptr;
+
 void Ssl::Helper::Init()
 {
     assert(ssl_crtd == NULL);
 
-    // we need to start ssl_crtd only if some port(s) need to bump SSL
+    // we need to start ssl_crtd only if some port(s) need to bump SSL *and* generate certificates
+    // TODO: generate host certificates for SNI enabled accel ports
     bool found = false;
     for (AnyP::PortCfgPointer s = HttpPortList; !found && s != NULL; s = s->next)
-        found = s->flags.tunnelSslBumping;
-    for (AnyP::PortCfgPointer s = HttpsPortList; !found && s != NULL; s = s->next)
-        found = s->flags.tunnelSslBumping;
+        found = s->flags.tunnelSslBumping && s->secure.generateHostCertificates;
     if (!found)
         return;
 
-    ssl_crtd = new helper("ssl_crtd");
+    ssl_crtd = new helper(Ssl::TheConfig.ssl_crtd);
     ssl_crtd->childs.updateLimits(Ssl::TheConfig.ssl_crtdChildren);
     ssl_crtd->ipc_type = IPC_STREAM;
     // The crtd messages may contain the eol ('\n') character. We are
@@ -60,26 +96,9 @@ void Ssl::Helper::Init()
     {
         char *tmp = xstrdup(Ssl::TheConfig.ssl_crtd);
         char *tmp_begin = tmp;
-        char * token = NULL;
-        bool db_path_was_found = false;
-        bool block_size_was_found = false;
-        char buffer[20] = "2048";
+        char *token = NULL;
         while ((token = strwordtok(NULL, &tmp))) {
             wordlistAdd(&ssl_crtd->cmdline, token);
-            if (!strcmp(token, "-b"))
-                block_size_was_found = true;
-            if (!strcmp(token, "-s")) {
-                db_path_was_found = true;
-            } else if (db_path_was_found) {
-                db_path_was_found = false;
-                int fs_block_size = 0;
-                storeDirGetBlkSize(token, &fs_block_size);
-                snprintf(buffer, sizeof(buffer), "%i", fs_block_size);
-            }
-        }
-        if (!block_size_was_found) {
-            wordlistAdd(&ssl_crtd->cmdline, "-b");
-            wordlistAdd(&ssl_crtd->cmdline, buffer);
         }
         safe_free(tmp_begin);
     }
@@ -96,49 +115,71 @@ void Ssl::Helper::Shutdown()
     ssl_crtd = NULL;
 }
 
-void Ssl::Helper::sslSubmit(CrtdMessage const & message, HLPCB * callback, void * data)
+void
+Ssl::Helper::Reconfigure()
 {
-    assert(ssl_crtd);
-
-    std::string msg = message.compose();
-    msg += '\n';
-    if (!ssl_crtd->trySubmit(msg.c_str(), callback, data)) {
-        ::Helper::Reply failReply;
-        failReply.result = ::Helper::BrokenHelper;
-        failReply.notes.add("message", "error 45 Temporary network problem, please retry later");
-        callback(data, failReply);
-        return;
-    }
+    Shutdown();
+    Init();
 }
-#endif //USE_SSL_CRTD
 
-Ssl::CertValidationHelper * Ssl::CertValidationHelper::GetInstance()
+void Ssl::Helper::Submit(CrtdMessage const & message, HLPCB * callback, void * data)
 {
-    static Ssl::CertValidationHelper sslHelper;
-    if (!Ssl::TheConfig.ssl_crt_validator)
-        return NULL;
-    return &sslHelper;
-}
+    SBuf rawMessage(message.compose().c_str()); // XXX: helpers cannot use SBuf
+    rawMessage.append("\n", 1);
 
-Ssl::CertValidationHelper::CertValidationHelper() : ssl_crt_validator(NULL)
-{
+    const auto pending = TheGeneratorRequests.find(rawMessage);
+    if (pending != TheGeneratorRequests.end()) {
+        pending->second->emplace(callback, data);
+        debugs(83, 5, "collapsed request from " << data << " onto " << *pending->second);
+        return;
+    }
+
+    GeneratorRequest *request = new GeneratorRequest;
+    request->query = rawMessage;
+    request->emplace(callback, data);
+    TheGeneratorRequests.emplace(request->query, request);
+    debugs(83, 5, "request from " << data << " as " << *request);
+    // ssl_crtd becomes nil if Squid is reconfigured without SslBump or
+    // certificate generation disabled in the new configuration
+    if (ssl_crtd && ssl_crtd->trySubmit(request->query.c_str(), HandleGeneratorReply, request))
+        return;
+
+    ::Helper::Reply failReply(::Helper::BrokenHelper);
+    failReply.notes.add("message", "error 45 Temporary network problem, please retry later");
+    HandleGeneratorReply(request, failReply);
 }
 
-Ssl::CertValidationHelper::~CertValidationHelper()
+/// receives helper response
+static void
+Ssl::HandleGeneratorReply(void *data, const ::Helper::Reply &reply)
 {
-    Shutdown();
+    const std::unique_ptr<Ssl::GeneratorRequest> request(static_cast<Ssl::GeneratorRequest*>(data));
+    assert(request);
+    const auto erased = TheGeneratorRequests.erase(request->query);
+    assert(erased);
+
+    for (auto &requestor: request->requestors) {
+        if (void *cbdata = requestor.data.validDone()) {
+            debugs(83, 5, "to " << cbdata << " in " << *request);
+            requestor.callback(cbdata, reply);
+        }
+    }
 }
+#endif //USE_SSL_CRTD
+
+helper *Ssl::CertValidationHelper::ssl_crt_validator = nullptr;
 
 void Ssl::CertValidationHelper::Init()
 {
+    if (!Ssl::TheConfig.ssl_crt_validator)
+        return;
+
     assert(ssl_crt_validator == NULL);
 
     // we need to start ssl_crtd only if some port(s) need to bump SSL
     bool found = false;
     for (AnyP::PortCfgPointer s = HttpPortList; !found && s != NULL; s = s->next)
         found = s->flags.tunnelSslBumping;
-    for (AnyP::PortCfgPointer s = HttpsPortList; !found && s != NULL; s = s->next)
-        found = s->flags.tunnelSslBumping;
     if (!found)
         return;
 
@@ -150,20 +191,30 @@ void Ssl::CertValidationHelper::Init()
     ssl_crt_validator->eom = '\1';
     assert(ssl_crt_validator->cmdline == NULL);
 
-    int ttl = 60;
-    size_t cache = 2048;
+    /* defaults */
+    int ttl = 3600; // 1 hour
+    size_t cache = 64*1024*1024; // 64 MB
     {
+        // TODO: Do this during parseConfigFile() for proper parsing, error handling
         char *tmp = xstrdup(Ssl::TheConfig.ssl_crt_validator);
         char *tmp_begin = tmp;
         char * token = NULL;
         bool parseParams = true;
         while ((token = strwordtok(NULL, &tmp))) {
             if (parseParams) {
-                if (strncmp(token, "ttl=", 4) == 0) {
-                    ttl = atoi(token + 4);
+                if (strcmp(token, "ttl=infinity") == 0) {
+                    ttl = std::numeric_limits<CacheType::Ttl>::max();
+                    continue;
+                } else if (strncmp(token, "ttl=", 4) == 0) {
+                    ttl = xatoi(token + 4);
+                    if (ttl < 0) {
+                        throw TextException(ToSBuf("Negative TTL in sslcrtvalidator_program ", Ssl::TheConfig.ssl_crt_validator,
+                                                   Debug::Extra, "For unlimited TTL, use ttl=infinity"),
+                                            Here());
+                    }
                     continue;
                 } else if (strncmp(token, "cache=", 6) == 0) {
-                    cache = atoi(token + 6);
+                    cache = xatoi(token + 6);
                     continue;
                 } else
                     parseParams = false;
@@ -176,7 +227,7 @@ void Ssl::CertValidationHelper::Init()
 
     //WARNING: initializing static member in an object initialization method
     assert(HelperCache == NULL);
-    HelperCache = new LruMap<Ssl::CertValidationResponse>(ttl, cache);
+    HelperCache = new CacheType(cache, ttl);
 }
 
 void Ssl::CertValidationHelper::Shutdown()
@@ -195,15 +246,21 @@ void Ssl::CertValidationHelper::Shutdown()
     HelperCache = NULL;
 }
 
+void
+Ssl::CertValidationHelper::Reconfigure()
+{
+    Shutdown();
+    Init();
+}
+
 class submitData
 {
     CBDATA_CLASS(submitData);
 
 public:
-    std::string query;
-    Ssl::CertValidationHelper::CVHCB *callback;
-    void *data;
-    SSL *ssl;
+    SBuf query;
+    AsyncCall::Pointer callback;
+    Security::SessionPointer ssl;
 };
 CBDATA_CLASS_INIT(submitData);
 
@@ -211,71 +268,75 @@ static void
 sslCrtvdHandleReplyWrapper(void *data, const ::Helper::Reply &reply)
 {
     Ssl::CertValidationMsg replyMsg(Ssl::CrtdMessage::REPLY);
-    Ssl::CertValidationResponse *validationResponse = new Ssl::CertValidationResponse;
     std::string error;
 
     submitData *crtdvdData = static_cast<submitData *>(data);
-    STACK_OF(X509) *peerCerts = SSL_get_peer_cert_chain(crtdvdData->ssl);
+    assert(crtdvdData->ssl.get());
+    Ssl::CertValidationResponse::Pointer validationResponse = new Ssl::CertValidationResponse(crtdvdData->ssl);
     if (reply.result == ::Helper::BrokenHelper) {
         debugs(83, DBG_IMPORTANT, "\"ssl_crtvd\" helper error response: " << reply.other().content());
         validationResponse->resultCode = ::Helper::BrokenHelper;
+    } else if (!reply.other().hasContent()) {
+        debugs(83, DBG_IMPORTANT, "\"ssl_crtvd\" helper returned NULL response");
+        validationResponse->resultCode = ::Helper::BrokenHelper;
     } else if (replyMsg.parse(reply.other().content(), reply.other().contentSize()) != Ssl::CrtdMessage::OK ||
-               !replyMsg.parseResponse(*validationResponse, peerCerts, error) ) {
+               !replyMsg.parseResponse(*validationResponse, error) ) {
         debugs(83, DBG_IMPORTANT, "WARNING: Reply from ssl_crtvd for " << " is incorrect");
         debugs(83, DBG_IMPORTANT, "Certificate cannot be validated. ssl_crtvd response: " << replyMsg.getBody());
         validationResponse->resultCode = ::Helper::BrokenHelper;
     } else
         validationResponse->resultCode = reply.result;
 
-    crtdvdData->callback(crtdvdData->data, *validationResponse);
+    Ssl::CertValidationHelper::CbDialer *dialer = dynamic_cast<Ssl::CertValidationHelper::CbDialer*>(crtdvdData->callback->getDialer());
+    Must(dialer);
+    dialer->arg1 = validationResponse;
+    ScheduleCallHere(crtdvdData->callback);
 
     if (Ssl::CertValidationHelper::HelperCache &&
             (validationResponse->resultCode == ::Helper::Okay || validationResponse->resultCode == ::Helper::Error)) {
-        Ssl::CertValidationHelper::HelperCache->add(crtdvdData->query.c_str(), validationResponse);
-    } else
-        delete validationResponse;
+        (void)Ssl::CertValidationHelper::HelperCache->add(crtdvdData->query, validationResponse);
+    }
 
-    cbdataReferenceDone(crtdvdData->data);
-    SSL_free(crtdvdData->ssl);
     delete crtdvdData;
 }
 
-void Ssl::CertValidationHelper::sslSubmit(Ssl::CertValidationRequest const &request, Ssl::CertValidationHelper::CVHCB * callback, void * data)
+void Ssl::CertValidationHelper::Submit(Ssl::CertValidationRequest const &request, AsyncCall::Pointer &callback)
 {
-    assert(ssl_crt_validator);
-
     Ssl::CertValidationMsg message(Ssl::CrtdMessage::REQUEST);
     message.setCode(Ssl::CertValidationMsg::code_cert_validate);
     message.composeRequest(request);
     debugs(83, 5, "SSL crtvd request: " << message.compose().c_str());
 
     submitData *crtdvdData = new submitData;
-    crtdvdData->query = message.compose();
-    crtdvdData->query += '\n';
+    crtdvdData->query.assign(message.compose().c_str());
+    crtdvdData->query.append('\n');
     crtdvdData->callback = callback;
-    crtdvdData->data = cbdataReference(data);
     crtdvdData->ssl = request.ssl;
-    CRYPTO_add(&crtdvdData->ssl->references,1,CRYPTO_LOCK_SSL);
-    Ssl::CertValidationResponse const*validationResponse;
+    Ssl::CertValidationResponse::Pointer const*validationResponse;
 
     if (CertValidationHelper::HelperCache &&
-            (validationResponse = CertValidationHelper::HelperCache->get(crtdvdData->query.c_str()))) {
-        callback(data, *validationResponse);
-        cbdataReferenceDone(crtdvdData->data);
-        SSL_free(crtdvdData->ssl);
+            (validationResponse = CertValidationHelper::HelperCache->get(crtdvdData->query))) {
+
+        CertValidationHelper::CbDialer *dialer = dynamic_cast<CertValidationHelper::CbDialer*>(callback->getDialer());
+        Must(dialer);
+        dialer->arg1 = *validationResponse;
+        ScheduleCallHere(callback);
         delete crtdvdData;
         return;
     }
 
-    if (!ssl_crt_validator->trySubmit(crtdvdData->query.c_str(), sslCrtvdHandleReplyWrapper, crtdvdData)) {
-        Ssl::CertValidationResponse resp;
-        resp.resultCode = ::Helper::BrokenHelper;
-        callback(data, resp);
-
-        cbdataReferenceDone(crtdvdData->data);
-        SSL_free(crtdvdData->ssl);
-        delete crtdvdData;
+    // ssl_crt_validator becomes nil if Squid is reconfigured with cert
+    // validator disabled in the new configuration
+    if (ssl_crt_validator && ssl_crt_validator->trySubmit(crtdvdData->query.c_str(), sslCrtvdHandleReplyWrapper, crtdvdData))
         return;
-    }
+
+    Ssl::CertValidationResponse::Pointer resp = new Ssl::CertValidationResponse(crtdvdData->ssl);
+    resp->resultCode = ::Helper::BrokenHelper;
+    Ssl::CertValidationHelper::CbDialer *dialer = dynamic_cast<Ssl::CertValidationHelper::CbDialer*>(callback->getDialer());
+    Must(dialer);
+    dialer->arg1 = resp;
+    ScheduleCallHere(callback);
+    delete crtdvdData;
+    return;
 }