/*
 * Copyright (C) 1996-2023 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 "acl/FilledChecklist.h"
#include "client_side.h"
#include "comm/Connection.h"
#include "comm/forward.h"
#include "debug/Messages.h"
#include "ExternalACLEntry.h"
#include "http/Stream.h"
#include "HttpReply.h"
#include "HttpRequest.h"
#include "SquidConfig.h"
#if USE_AUTH
#include "auth/AclProxyAuth.h"
#include "auth/UserRequest.h"
#endif

CBDATA_CLASS_INIT(ACLFilledChecklist);

ACLFilledChecklist::ACLFilledChecklist() :
    dst_rdns(nullptr),
    request (nullptr),
    reply (nullptr),
#if USE_AUTH
    auth_user_request (nullptr),
#endif
#if SQUID_SNMP
    snmp_community(nullptr),
#endif
    requestErrorType(ERR_MAX),
    conn_(nullptr),
    fd_(-1),
    destinationDomainChecked_(false),
    sourceDomainChecked_(false)
{
    my_addr.setEmpty();
    src_addr.setEmpty();
    dst_addr.setEmpty();
    rfc931[0] = '\0';
}

ACLFilledChecklist::~ACLFilledChecklist()
{
    assert (!asyncInProgress());

    safe_free(dst_rdns); // created by xstrdup().

    HTTPMSGUNLOCK(request);

    HTTPMSGUNLOCK(reply);

    cbdataReferenceDone(conn_);

    debugs(28, 4, "ACLFilledChecklist destroyed " << this);
}

static void
showDebugWarning(const char *msg)
{
    static uint16_t count = 0;
    if (count > 10)
        return;

    ++count;
    debugs(28, Important(58), "ERROR: ALE missing " << msg);
}

void
ACLFilledChecklist::verifyAle() const
{
    // make sure the ALE fields used by Format::assemble to
    // fill the old external_acl_type codes are set if any
    // data on them exists in the Checklist

    if (!al->cache.port && conn()) {
        showDebugWarning("listening port");
        al->cache.port = conn()->port;
    }

    if (request) {
        if (!al->request) {
            showDebugWarning("HttpRequest object");
            // XXX: al->request should be original,
            // but the request may be already adapted
            al->request = request;
            HTTPMSGLOCK(al->request);
        }

        if (!al->adapted_request) {
            showDebugWarning("adapted HttpRequest object");
            al->adapted_request = request;
            HTTPMSGLOCK(al->adapted_request);
        }

        if (al->url.isEmpty()) {
            showDebugWarning("URL");
            // XXX: al->url should be the request URL from client,
            // but request->url may be different (e.g.,redirected)
            al->url = request->effectiveRequestUri();
        }
    }

    if (reply && !al->reply) {
        showDebugWarning("HttpReply object");
        al->reply = reply;
    }

#if USE_IDENT
    if (*rfc931 && !al->cache.rfc931) {
        showDebugWarning("IDENT");
        al->cache.rfc931 = xstrdup(rfc931);
    }
#endif
}

void
ACLFilledChecklist::syncAle(HttpRequest *adaptedRequest, const char *logUri) const
{
    if (!al)
        return;
    if (adaptedRequest && !al->adapted_request) {
        al->adapted_request = adaptedRequest;
        HTTPMSGLOCK(al->adapted_request);
    }
    if (logUri && al->url.isEmpty())
        al->url = logUri;
}

ConnStateData *
ACLFilledChecklist::conn() const
{
    return cbdataReferenceValid(conn_) ? conn_ : nullptr;
}

void
ACLFilledChecklist::setConn(ConnStateData *aConn)
{
    if (conn_ == aConn)
        return; // no new information

    // no conn_ replacement/removal to reduce inconsistent fill concerns
    assert(!conn_);
    assert(aConn);

    // To reduce inconsistent fill concerns, we should be the only ones calling
    // fillConnectionLevelDetails(). Set conn_ first so that the filling method
    // can detect (some) direct calls from others.
    conn_ = cbdataReference(aConn);
    aConn->fillConnectionLevelDetails(*this);
}

int
ACLFilledChecklist::fd() const
{
    const auto c = conn();
    return (c && c->clientConnection) ? c->clientConnection->fd : fd_;
}

void
ACLFilledChecklist::fd(int aDescriptor)
{
    const auto c = conn();
    assert(!c || !c->clientConnection || c->clientConnection->fd == aDescriptor);
    fd_ = aDescriptor;
}

bool
ACLFilledChecklist::destinationDomainChecked() const
{
    return destinationDomainChecked_;
}

void
ACLFilledChecklist::markDestinationDomainChecked()
{
    assert (!finished() && !destinationDomainChecked());
    destinationDomainChecked_ = true;
}

bool
ACLFilledChecklist::sourceDomainChecked() const
{
    return sourceDomainChecked_;
}

void
ACLFilledChecklist::markSourceDomainChecked()
{
    assert (!finished() && !sourceDomainChecked());
    sourceDomainChecked_ = true;
}

/*
 * There are two common ACLFilledChecklist lifecycles paths:
 *
 * A) Using aclCheckFast(): The caller creates an ACLFilledChecklist object
 *    on stack and calls aclCheckFast().
 *
 * B) Using aclNBCheck() and callbacks: The caller allocates an
 *    ACLFilledChecklist object (via operator new) and passes it to
 *    aclNBCheck(). Control eventually passes to ACLChecklist::checkCallback(),
 *    which will invoke the callback function as requested by the
 *    original caller of aclNBCheck().  This callback function must
 *    *not* delete the list.  After the callback function returns,
 *    checkCallback() will delete the list (i.e., self).
 */
ACLFilledChecklist::ACLFilledChecklist(const acl_access *A, HttpRequest *http_request, const char *ident):
    dst_rdns(nullptr),
    request(nullptr),
    reply(nullptr),
#if USE_AUTH
    auth_user_request(nullptr),
#endif
#if SQUID_SNMP
    snmp_community(nullptr),
#endif
    requestErrorType(ERR_MAX),
    conn_(nullptr),
    fd_(-1),
    destinationDomainChecked_(false),
    sourceDomainChecked_(false)
{
    my_addr.setEmpty();
    src_addr.setEmpty();
    dst_addr.setEmpty();
    rfc931[0] = '\0';

    changeAcl(A);
    setRequest(http_request);
    setIdent(ident);
}

void ACLFilledChecklist::setRequest(HttpRequest *httpRequest)
{
    assert(!request);
    if (httpRequest) {
        request = httpRequest;
        HTTPMSGLOCK(request);
#if FOLLOW_X_FORWARDED_FOR
        if (Config.onoff.acl_uses_indirect_client)
            src_addr = request->indirect_client_addr;
        else
#endif /* FOLLOW_X_FORWARDED_FOR */
            src_addr = request->client_addr;
        my_addr = request->my_addr;

        if (const auto cmgr = request->clientConnectionManager.get())
            setConn(cmgr);
    }
}

void
ACLFilledChecklist::setIdent(const char *ident)
{
#if USE_IDENT
    assert(!rfc931[0]);
    if (ident)
        xstrncpy(rfc931, ident, USER_IDENT_SZ);
#else
    (void)ident;
#endif
}

