*/
#include "squid.h"
+#include "acl/Gadgets.h"
#include "base/TextException.h"
#include "comm/Write.h"
#include "Server.h"
// received adapted response headers (body may follow)
void
-ServerStateData::noteAdaptationAnswer(HttpMsg *msg)
+ServerStateData::noteAdaptationAnswer(const Adaptation::Answer &answer)
{
clearAdaptation(adaptedHeadSource); // we do not expect more messages
+ switch (answer.kind) {
+ case Adaptation::Answer::akForward:
+ handleAdaptedHeader(answer.message);
+ break;
+
+ case Adaptation::Answer::akBlock:
+ handleAdaptationBlocked(answer);
+ break;
+
+ case Adaptation::Answer::akError:
+ handleAdaptationAborted(!answer.final);
+ break;
+ }
+}
+
+void
+ServerStateData::handleAdaptedHeader(HttpMsg *msg)
+{
if (abortOnBadEntry("entry went bad while waiting for adapted headers"))
return;
}
}
-// will not receive adapted response headers (and, hence, body)
-void
-ServerStateData::noteAdaptationQueryAbort(bool final)
-{
- clearAdaptation(adaptedHeadSource);
- handleAdaptationAborted(!final);
-}
-
// more adapted response body is available
void
ServerStateData::handleMoreAdaptedBodyAvailable()
abortTransaction("ICAP failure");
}
+// adaptation service wants us to deny HTTP client access to this response
+void
+ServerStateData::handleAdaptationBlocked(const Adaptation::Answer &answer)
+{
+ debugs(11,5, HERE << "handleAdaptationBlocked: " << answer.ruleId);
+
+ if (abortOnBadEntry("entry went bad while ICAP aborted"))
+ return;
+
+ if (!entry->isEmpty()) { // too late to block (should not really happen)
+ if (request)
+ request->detailError(ERR_ICAP_FAILURE, ERR_DETAIL_RESPMOD_BLOCK_LATE);
+ abortTransaction("late adaptation block");
+ return;
+ }
+
+ debugs(11,7, HERE << "creating adaptation block response");
+
+ err_type page_id =
+ aclGetDenyInfoPage(&Config.denyInfoList, answer.ruleId.termedBuf(), 1);
+ if (page_id == ERR_NONE)
+ page_id = ERR_ACCESS_DENIED;
+
+ ErrorState *err = errorCon(page_id, HTTP_FORBIDDEN, request);
+ err->xerrno = ERR_DETAIL_RESPMOD_BLOCK_EARLY;
+ fwd->fail(err);
+ fwd->dontRetry(true);
+
+ abortTransaction("timely adaptation block");
+}
+
void
ServerStateData::adaptationAclCheckDone(Adaptation::ServiceGroupPointer group)
{
static void adaptationAclCheckDoneWrapper(Adaptation::ServiceGroupPointer group, void *data);
// ICAPInitiator: start an ICAP transaction and receive adapted headers.
- virtual void noteAdaptationAnswer(HttpMsg *message);
- virtual void noteAdaptationQueryAbort(bool final);
+ virtual void noteAdaptationAnswer(const Adaptation::Answer &answer);
// BodyProducer: provide virgin response body to ICAP.
virtual void noteMoreBodySpaceAvailable(BodyPipe::Pointer );
void handleAdaptedBodyProductionEnded();
void handleAdaptedBodyProducerAborted();
+ void handleAdaptedHeader(HttpMsg *msg);
void handleAdaptationCompleted();
+ void handleAdaptationBlocked(const Adaptation::Answer &answer);
void handleAdaptationAborted(bool bypassable = false);
#endif
#include "adaptation/Initiate.h"
#include "base/AsyncJobCalls.h"
-namespace Adaptation
-{
-
-// AdaptInitiator::noteAdaptionAnswer Dialer locks/unlocks the message in transit
-// TODO: replace HTTPMSGLOCK with general RefCounting and delete this class
-class AnswerDialer: public UnaryMemFunT<Initiator, HttpMsg*>
-{
-public:
- typedef UnaryMemFunT<Initiator, HttpMsg*> Parent;
-
- AnswerDialer(const Parent::JobPointer &job, Parent::Method meth,
- HttpMsg *msg): Parent(job, meth, msg) { HTTPMSGLOCK(arg1); }
- AnswerDialer(const AnswerDialer &d): Parent(d) { HTTPMSGLOCK(arg1); }
- virtual ~AnswerDialer() { HTTPMSGUNLOCK(arg1); }
-
-private:
- AnswerDialer &operator =(const AnswerDialer &); // not implemented
-};
-
-} // namespace Adaptation
-
-
-/* Initiate */
Adaptation::Initiate::Initiate(const char *aTypeName): AsyncJob(aTypeName)
{
theInitiator.clear();
}
-void Adaptation::Initiate::sendAnswer(HttpMsg *msg)
+void Adaptation::Initiate::sendAnswer(const Answer &answer)
{
- assert(msg);
+ typedef UnaryMemFunT<Initiator, Answer, const Answer &> MyDialer;
CallJob(93, 5, __FILE__, __LINE__, "Initiator::noteAdaptationAnswer",
- AnswerDialer(theInitiator, &Initiator::noteAdaptationAnswer, msg));
+ MyDialer(theInitiator, &Initiator::noteAdaptationAnswer, answer));
clearInitiator();
}
void Adaptation::Initiate::tellQueryAborted(bool final)
{
- CallJobHere1(93, 5, theInitiator,
- Initiator, noteAdaptationQueryAbort, final);
- clearInitiator();
+ sendAnswer(Answer::Error(final));
}
const char *Adaptation::Initiate::status() const
#include "base/CbcPointer.h"
#include "adaptation/forward.h"
-class HttpMsg;
-
namespace Adaptation
{
virtual void noteInitiatorAborted() = 0;
protected:
- void sendAnswer(HttpMsg *msg); // send to the initiator
+ void sendAnswer(const Answer &answer); // send to the initiator
void tellQueryAborted(bool final); // tell initiator
void clearInitiator(); // used by noteInitiatorAborted; TODO: make private
CallJobHere(93, 5, x, Initiate, noteInitiatorAborted);
clearAdaptation(x);
}
+
+
+/* Adaptation::Answer */
+
+// TODO: Move to src/adaptation/Answer.*
+
+Adaptation::Answer
+Adaptation::Answer::Error(bool final)
+{
+ Answer answer(akError);
+ answer.final = final;
+ debugs(93, 4, HERE << "error: " << final);
+ return answer;
+}
+
+Adaptation::Answer
+Adaptation::Answer::Forward(HttpMsg *aMsg)
+{
+ Answer answer(akForward);
+ answer.message = aMsg;
+ debugs(93, 4, HERE << "forwarding: " << (void*)aMsg);
+ return answer;
+}
+
+
+Adaptation::Answer
+Adaptation::Answer::Block(const String &aRule)
+{
+ Answer answer(akBlock);
+ answer.ruleId = aRule;
+ debugs(93, 4, HERE << "blocking rule: " << aRule);
+ return answer;
+}
+
+std::ostream &
+Adaptation::Answer::print(std::ostream &os) const
+{
+ return os << kind; // TODO: add more details
+}
+
+Adaptation::Answer::Answer(Kind aKind): final(true), kind(aKind)
+{
+}
#include "base/AsyncJob.h"
#include "base/CbcPointer.h"
#include "adaptation/forward.h"
+#include "HttpMsg.h"
+
+#include <iosfwd>
/*
* The ICAP Initiator is an ICAP vectoring point that initates ICAP
* or aborting an ICAP transaction.
*/
-class HttpMsg;
-
namespace Adaptation
{
+/// summarizes adaptation service answer for the noteAdaptationAnswer() API
+class Answer {
+public:
+ /// helps interpret other members without a class hierarchy
+ typedef enum {
+ akForward, ///< forward the supplied adapted HTTP message
+ akBlock, ///< block or deny the master xaction; see authority
+ akError, ///< no adapted message will come; see bypassable
+ } Kind;
+
+ static Answer Error(bool final); ///< create an akError answer
+ static Answer Forward(HttpMsg *aMsg); ///< create an akForward answer
+ static Answer Block(const String &aRule); ///< create an akBlock answer
+
+ std::ostream &print(std::ostream &os) const;
+
+public:
+ HttpMsgPointerT<HttpMsg> message; ///< HTTP request or response to forward
+ String ruleId; ///< ACL (or similar rule) name that blocked forwarding
+ bool final; ///< whether the error, if any, cannot be bypassed
+ Kind kind; ///< the type of the answer
+
+private:
+ explicit Answer(Kind aKind); ///< use static creators instead
+};
+
+inline
+std::ostream &operator <<(std::ostream &os, const Answer &answer)
+{
+ return answer.print(os);
+}
+
class Initiator: virtual public AsyncJob
{
public:
Initiator(): AsyncJob("Initiator") {}
virtual ~Initiator() {}
- // called when ICAP response headers are successfully interpreted
- virtual void noteAdaptationAnswer(HttpMsg *message) = 0;
-
- // called when valid ICAP response headers are no longer expected
- // the final parameter is set to disable bypass or retries
- virtual void noteAdaptationQueryAbort(bool final) = 0;
+ /// called with the initial adaptation decision (adapt, block, error);
+ /// virgin and/or adapted body transmission may continue after this
+ virtual void noteAdaptationAnswer(const Answer &answer) = 0;
protected:
///< starts freshly created initiate and returns a safe pointer to it
Must(!theLauncher);
if (thePlan.exhausted()) { // nothing more to do
- sendAnswer(theMsg);
+ sendAnswer(Answer::Forward(theMsg));
Must(done());
return;
}
Must(!done());
}
-void Adaptation::Iterator::noteAdaptationAnswer(HttpMsg *aMsg)
+void
+Adaptation::Iterator::noteAdaptationAnswer(const Answer &answer)
+{
+ switch (answer.kind) {
+ case Answer::akForward:
+ handleAdaptedHeader(answer.message);
+ break;
+
+ case Answer::akBlock:
+ handleAdaptationBlock(answer);
+ break;
+
+ case Answer::akError:
+ handleAdaptationError(answer.final);
+ break;
+ }
+}
+
+void
+Adaptation::Iterator::handleAdaptedHeader(HttpMsg *aMsg)
{
// set theCause if we switched to request satisfaction mode
if (!theCause) { // probably sent a request message
mustStop("initiator gone");
}
-void Adaptation::Iterator::noteAdaptationQueryAbort(bool final)
+void Adaptation::Iterator::handleAdaptationBlock(const Answer &answer)
+{
+ debugs(93,5, HERE << "blocked by " << answer);
+ clearAdaptation(theLauncher);
+ updatePlan(false);
+ sendAnswer(answer);
+ mustStop("blocked");
+}
+
+void Adaptation::Iterator::handleAdaptationError(bool final)
{
debugs(93,5, HERE << "final: " << final << " plan: " << thePlan);
clearAdaptation(theLauncher);
if (canIgnore && srcIntact && adapted) {
debugs(85,3, HERE << "responding with older adapted msg");
- sendAnswer(theMsg);
+ sendAnswer(Answer::Forward(theMsg));
mustStop("sent older adapted msg");
return;
}
void noteInitiatorAborted();
// Adaptation::Initiator: asynchronous communication with the current launcher
- virtual void noteAdaptationAnswer(HttpMsg *message);
- virtual void noteAdaptationQueryAbort(bool final);
+ virtual void noteAdaptationAnswer(const Answer &answer);
protected:
// Adaptation::Initiate API implementation
/// creates service filter for the current step
ServiceFilter filter() const;
+ void handleAdaptedHeader(HttpMsg *msg);
+ void handleAdaptationBlock(const Answer &answer);
+ void handleAdaptationError(bool final);
+
ServiceGroupPointer theGroup; ///< the service group we are iterating
ServicePlan thePlan; ///< which services to use and in what order
HttpMsg *theMsg; ///< the message being adapted (virgin for each step)
#include "HttpReply.h"
#include "SquidTime.h"
#include "adaptation/ecap/XactionRep.h"
+#include "adaptation/Initiator.h"
#include "base/TextException.h"
CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Ecap::XactionRep, XactionRep);
Adaptation::Ecap::XactionRep::dropVirgin(const char *reason)
{
debugs(93,4, HERE << "because " << reason << "; status:" << status());
- Must(proxyingVb = opOn);
BodyPipePointer &p = theVirginRep.raw().body_pipe;
Must(p != NULL);
- Must(p->stillConsuming(this));
- stopConsumingFrom(p);
p->enableAutoConsumption();
- proxyingVb = opComplete;
+
+ if (proxyingVb == opOn) {
+ Must(p->stillConsuming(this));
+ stopConsumingFrom(p);
+ proxyingVb = opComplete;
+ } else {
+ Must(!p->stillConsuming(this));
+ if (proxyingVb == opUndecided)
+ proxyingVb = opNever;
+ }
+
canAccessVb = false;
// called from adapter handler so does not inform adapter
proxyingVb = opNever;
}
- sendAnswer(clone);
+ sendAnswer(Answer::Forward(clone));
Must(done());
}
HttpMsg *msg = answer().header;
if (!theAnswerRep->body()) { // final, bodyless answer
proxyingAb = opNever;
- sendAnswer(msg);
+ sendAnswer(Answer::Forward(msg));
} else { // got answer headers but need to handle body
proxyingAb = opOn;
Must(!msg->body_pipe); // only host can set body pipes
rep->tieBody(this); // sets us as a producer
Must(msg->body_pipe != NULL); // check tieBody
- sendAnswer(msg);
+ sendAnswer(Answer::Forward(msg));
debugs(93,4, HERE << "adapter will produce body" << status());
theMaster->abMake(); // libecap will produce
}
}
+void
+Adaptation::Ecap::XactionRep::blockVirgin()
+{
+ debugs(93,3, HERE << status());
+ Must(proxyingAb == opUndecided);
+ proxyingAb = opNever;
+
+ dropVirgin("blockVirgin");
+
+ sendAnswer(Answer::Block(service().cfg().key));
+ Must(done());
+}
+
void
Adaptation::Ecap::XactionRep::vbDiscard()
{
Adaptation::Ecap::XactionRep::vbStopMaking()
{
// if adapter does not need vb, we do not need to receive it
- if (proxyingVb == opOn)
- dropVirgin("vbStopMaking");
+ dropVirgin("vbStopMaking");
Must(proxyingVb == opComplete);
}
virtual libecap::Message &adapted();
virtual void useVirgin();
virtual void useAdapted(const libecap::shared_ptr<libecap::Message> &msg);
+ virtual void blockVirgin();
virtual void adaptationDelayed(const libecap::Delay &);
virtual void adaptationAborted();
virtual void vbDiscard();
class ServicePlan;
class ServiceFilter;
class Message;
+class Answer;
typedef RefCount<Service> ServicePointer;
typedef RefCount<ServiceGroup> ServiceGroupPointer;
Must(initiated(theXaction));
}
-void Adaptation::Icap::Launcher::noteAdaptationAnswer(HttpMsg *message)
+void Adaptation::Icap::Launcher::noteAdaptationAnswer(const Answer &answer)
{
- sendAnswer(message);
+ debugs(93,5, HERE << "launches: " << theLaunches << " answer: " << answer);
+
+ // XXX: akError is unused by ICAPXaction in favor of noteXactAbort()
+ Must(answer.kind != Answer::akError);
+
+ sendAnswer(answer);
clearAdaptation(theXaction);
Must(done());
- debugs(93,3, HERE << "Adaptation::Icap::Launcher::noteAdaptationAnswer exiting ");
}
void Adaptation::Icap::Launcher::noteInitiatorAborted()
}
-// XXX: this call is unused by ICAPXaction in favor of ICAPLauncher::noteXactAbort
-void Adaptation::Icap::Launcher::noteAdaptationQueryAbort(bool final)
-{
- debugs(93,5, HERE << "launches: " << theLaunches << "; final: " << final);
- clearAdaptation(theXaction);
-
- Must(done()); // swanSong will notify the initiator
-}
-
void Adaptation::Icap::Launcher::noteXactAbort(XactAbortInfo info)
{
debugs(93,5, HERE << "theXaction:" << theXaction << " launches: " << theLaunches);
void noteInitiatorAborted();
// Adaptation::Initiator: asynchronous communication with the current transaction
- virtual void noteAdaptationAnswer(HttpMsg *message);
+ virtual void noteAdaptationAnswer(const Answer &answer);
virtual void noteXactAbort(XactAbortInfo info);
private:
bool canRetry(XactAbortInfo &info) const; //< true if can retry in the case of persistent connection failures
bool canRepeat(XactAbortInfo &info) const; //< true if can repeat in the case of no or unsatisfactory response
- virtual void noteAdaptationQueryAbort(bool final);
protected:
// Adaptation::Initiate API implementation
{
disableRepeats("sent headers");
disableBypass("sent headers", true);
- sendAnswer(adapted.header);
+ sendAnswer(Answer::Forward(adapted.header));
if (state.sending == State::sendingVirgin)
echoMore();
debugs(93, 7, HERE << "readAll=" << readAll);
icap_tio_finish = current_time;
setOutcome(xoOpt);
- sendAnswer(icapReply);
+ sendAnswer(Answer::Forward(icapReply));
Must(done()); // there should be nothing else to do
return;
}
}
// we are receiving ICAP OPTIONS response headers here or NULL on failures
-void Adaptation::Icap::ServiceRep::noteAdaptationAnswer(HttpMsg *msg)
+void Adaptation::Icap::ServiceRep::noteAdaptationAnswer(const Answer &answer)
{
Must(initiated(theOptionsFetcher));
clearAdaptation(theOptionsFetcher);
+ if (answer.kind == Answer::akError) {
+ debugs(93,3, HERE << "failed to fetch options " << status());
+ handleNewOptions(0);
+ return;
+ }
+
+ Must(answer.kind == Answer::akForward); // no akBlock for OPTIONS requests
+ HttpMsg *msg = answer.message;
Must(msg);
debugs(93,5, HERE << "is interpreting new options " << status());
handleNewOptions(newOptions);
}
-void Adaptation::Icap::ServiceRep::noteAdaptationQueryAbort(bool)
-{
- Must(initiated(theOptionsFetcher));
- clearAdaptation(theOptionsFetcher);
-
- debugs(93,3, HERE << "failed to fetch options " << status());
- handleNewOptions(0);
-}
-
// we (a) must keep trying to get OPTIONS and (b) are RefCounted so we
// must keep our job alive (XXX: until nobody needs us)
void Adaptation::Icap::ServiceRep::callException(const std::exception &e)
void noteTimeToNotify();
// receive either an ICAP OPTIONS response header or an abort message
- virtual void noteAdaptationAnswer(HttpMsg *msg);
- virtual void noteAdaptationQueryAbort(bool);
+ virtual void noteAdaptationAnswer(const Answer &answer);
private:
// stores Prepare() callback info
the first authentication related acl encountered
- When none of the http_access lines matches. It's then the last
acl processed on the last http_access line.
+ - When the decision to deny access was made by an adaptation service,
+ the acl name is the corresponding eCAP or ICAP service_name.
NP: If providing your own custom error pages with error_directory
you may also specify them by your custom file name:
}
void
-ClientHttpRequest::noteAdaptationAnswer(HttpMsg *msg)
+ClientHttpRequest::noteAdaptationAnswer(const Adaptation::Answer &answer)
{
assert(cbdataReferenceValid(this)); // indicates bug
+ clearAdaptation(virginHeadSource);
+ assert(!adaptedBodySource);
+
+ switch (answer.kind) {
+ case Adaptation::Answer::akForward:
+ handleAdaptedHeader(answer.message);
+ break;
+
+ case Adaptation::Answer::akBlock:
+ handleAdaptationBlock(answer);
+ break;
+
+ case Adaptation::Answer::akError:
+ handleAdaptationFailure(ERR_DETAIL_CLT_REQMOD_ABORT, !answer.final);
+ break;
+ }
+}
+
+void
+ClientHttpRequest::handleAdaptedHeader(HttpMsg *msg)
+{
assert(msg);
if (HttpRequest *new_req = dynamic_cast<HttpRequest*>(msg)) {
}
void
-ClientHttpRequest::noteAdaptationQueryAbort(bool final)
+ClientHttpRequest::handleAdaptationBlock(const Adaptation::Answer &answer)
{
- clearAdaptation(virginHeadSource);
- assert(!adaptedBodySource);
- handleAdaptationFailure(ERR_DETAIL_CLT_REQMOD_ABORT, !final);
+ request->detailError(ERR_ACCESS_DENIED, ERR_DETAIL_REQMOD_BLOCK);
+ AclMatchedName = answer.ruleId.termedBuf();
+ assert(calloutContext);
+ calloutContext->clientAccessCheckDone(ACCESS_DENIED);
+ AclMatchedName = NULL;
}
void
private:
// Adaptation::Initiator API
- virtual void noteAdaptationAnswer(HttpMsg *message);
- virtual void noteAdaptationQueryAbort(bool final);
+ virtual void noteAdaptationAnswer(const Adaptation::Answer &answer);
+ void handleAdaptedHeader(HttpMsg *msg);
+ void handleAdaptationBlock(const Adaptation::Answer &answer);
// BodyConsumer API, called by BodyPipe
virtual void noteMoreBodyDataAvailable(BodyPipe::Pointer);
ERR_DETAIL_CLT_REQMOD_RESP_BODY, // client-side detected REQMOD satisfaction reply body failure
ERR_DETAIL_ICAP_RESPMOD_EARLY, // RESPMOD failed w/o store entry
ERR_DETAIL_ICAP_RESPMOD_LATE, // RESPMOD failed with a store entry
+ ERR_DETAIL_REQMOD_BLOCK, // REQMOD denied client access
+ ERR_DETAIL_RESPMOD_BLOCK_EARLY, // RESPMOD denied client access to HTTP response, before any part of the response was sent
+ ERR_DETAIL_RESPMOD_BLOCK_LATE, // RESPMOD denied client access to HTTP response, after [a part of] the response was sent
ERR_DETAIL_ICAP_XACT_START, // transaction start failure
ERR_DETAIL_ICAP_XACT_BODY_CONSUMER_ABORT, // transaction body consumer gone
ERR_DETAIL_ICAP_INIT_GONE, // initiator gone