Fallout from audit of access control checks.
- Some got sensible defaults added
- many got slightly more optimized defaults
- documented the ACLChecklist interface and some API cleanups
};
};
+/// \ingroup ACLAPI
+typedef enum {
+ ACCESS_DENIED,
+ ACCESS_ALLOWED,
+ ACCESS_REQ_PROXY_AUTH
+} allow_t;
+
/// \ingroup ACLAPI
class acl_access
{
ConnStateData *
-ACLChecklist::conn()
+ACLChecklist::conn() const
{
return conn_;
}
return state_;
}
+/**
+ * Kick off a non-blocking (slow) ACL access list test
+ *
+ * NP: this should probably be made Async now.
+ */
void
ACLChecklist::nonBlockingCheck(PF * callback_, void *callback_data_)
{
#ifndef SQUID_ACLCHECKLIST_H
#define SQUID_ACLCHECKLIST_H
-#include "typedefs.h"
-#include "client_side.h"
-#include "structs.h"
+//#include "typedefs.h"
+//#include "client_side.h"
+//#include "structs.h"
-class ExternalACLEntry;
+#include "ACL.h"
+class AuthUserRequest;
+class ExternalACLEntry;
class ConnStateData;
/// \ingroup ACLAPI
};
+public: /* operators */
void *operator new(size_t);
void operator delete(void *);
ACLChecklist();
~ACLChecklist();
- /* To cause link failures if assignment attempted */
+ /** NP: To cause link failures if assignment attempted */
ACLChecklist (ACLChecklist const &);
+ /** NP: To cause link failures if assignment attempted */
ACLChecklist &operator=(ACLChecklist const &);
+public: /* API methods */
+
+ /**
+ * Trigger off a non-blocking access check for a set of *_access options..
+ * The callback specified will be called with true/false
+ * when the results of the ACL tests are known.
+ */
void nonBlockingCheck(PF * callback, void *callback_data);
+
+ /**
+ * Trigger a blocking access check for a set of *_access options.
+ *
+ * ACLs which cannot be satisfied directly from available data are ignored.
+ * This means any proxy_auth, external_acl, DNS lookups, Ident lookups etc
+ * which have not already been performed and cached will not be checked.
+ *
+ * If there is no access list to check the default is to return DENIED.
+ * However callers should perform their own check and default based on local
+ * knowledge of the ACL usage rather than depend on this default.
+ * That will also save on work setting up ACLChecklist fields for a no-op.
+ *
+ * \retval 1/true Access Allowed
+ * \retval 0/false Access Denied
+ */
int fastCheck();
- void checkCallback(allow_t answer);
- void preCheck();
+
+ /**
+ * Trigger a blocking access check for a single ACL line (a AND b AND c).
+ *
+ * ACLs which cannot be satisfied directly from available data are ignored.
+ * This means any proxy_auth, external_acl, DNS lookups, Ident lookups etc
+ * which have not already been performed and cached will not be checked.
+ *
+ * \retval 1/true Access Allowed
+ * \retval 0/false Access Denied
+ */
_SQUID_INLINE_ bool matchAclListFast(const ACLList * list);
- ConnStateData * conn();
- int fd() const; // uses conn() if available
- // set either conn or FD
+ /**
+ * Attempt to check the current checklist against current data.
+ * This is the core routine behind all ACL test routines.
+ * As much as possible of current tests are performed immediately
+ * and the result is maybe delayed to wait for async lookups.
+ *
+ * When all tests are done callback is presented with one of:
+ * \item ACCESS_ALLOWED Access explicitly Allowed
+ * \item ACCESS_DENIED Access explicitly Denied
+ */
+ void check();
+
+ ConnStateData * conn() const;
+
+ /// uses conn() if available
+ int fd() const;
+
+ /// set either conn
void conn(ConnStateData *);
+ /// set FD
void fd(int aDescriptor);
+/* Accessors used by internal ACL stuff */
+
int authenticated();
bool asyncInProgress() const;
void asyncInProgress(bool const);
+
bool finished() const;
void markFinished();
- void check();
+
allow_t const & currentAnswer() const;
void currentAnswer(allow_t const);
+
+ void changeState(AsyncState *);
+ AsyncState *asyncState() const;
+
+private: /* NP: only used internally */
+
+ void checkCallback(allow_t answer);
void checkAccessList();
void checkForAsync();
- void changeState (AsyncState *);
- AsyncState *asyncState() const;
+
+public: /* checklist available data */
const acl_access *accessList;
struct peer *dst_peer;
HttpRequest *request;
+
/* for acls that look at reply data */
HttpReply *reply;
char rfc931[USER_IDENT_SZ];
PF *callback;
void *callback_data;
ExternalACLEntry *extacl_entry;
+
bool destinationDomainChecked() const;
void markDestinationDomainChecked();
bool sourceDomainChecked() const;
void markSourceDomainChecked();
-private:
+private: /* internal methods */
+ void preCheck();
void matchAclList(const ACLList * list, bool const fast);
void matchAclListSlow(const ACLList * list);
CBDATA_CLASS(ACLChecklist);
- ConnStateData * conn_; /* hack for ident and NTLM */
- int fd_; // may be available when conn_ is not
+
+ ConnStateData * conn_; /**< hack for ident and NTLM */
+ int fd_; /**< may be available when conn_ is not */
bool async_;
bool finished_;
allow_t allow_;
#include "ACLStringData.h"
#include "ACLChecklist.h"
+/* for ConnStateData */
+#include "client_side.h"
+
ACL::Prototype ACLMyPortName::RegistryProtoype(&ACLMyPortName::RegistryEntry_, "myportname");
}
for (pool = 0; pool < DelayPools::pools(); pool++) {
+
+ /* pools require explicit 'allow' to assign a client into them */
+ if (!DelayPools::delay_data[pool].access) {
+ debugs(77, DBG_IMPORTANT, "delay_pool " << pool <<
+ " has no delay_access configured. This means that no clients will ever use it.");
+ continue;
+ }
+
ACLChecklist ch;
#if FOLLOW_X_FORWARDED_FOR
if (Config.onoff.delay_pool_uses_indirect_client)
/* cbdataReferenceDone() happens in either fastCheck() or ~ACLCheckList */
- if (DelayPools::delay_data[pool].theComposite().getRaw() &&
- ch.fastCheck()) {
+ if (DelayPools::delay_data[pool].theComposite().getRaw() && ch.fastCheck()) {
+
DelayId result (pool + 1);
CompositePoolNode::CompositeSelectionDetails details;
details.src_addr = ch.src_addr;
memset(mask, value, sizeof(*mask));
}
-/* calculates a bit mask of a given array; does not reset mask! */
+/** calculates a bit mask of a given array; does not reset mask! */
void
httpHeaderCalcMask(HttpHeaderMask * mask, http_hdr_type http_hdr_type_enums[], size_t count)
{
return res;
}
-/* returns true iff "m" is a member of the list */
+/** returns true iff "m" is a member of the list */
int
strListIsMember(const String * list, const char *m, char del)
{
return 0;
}
-/* returns true iff "s" is a substring of a member of the list */
+/** returns true iff "s" is a substring of a member of the list */
int
strListIsSubstr(const String * list, const char *s, char del)
{
assert(list && del);
return list->pos(s) != 0;
- /*
+ /** \note
* Note: the original code with a loop is broken because it uses strstr()
* instead of strnstr(). If 's' contains a 'del', strListIsSubstr() may
* return true when it should not. If 's' does not contain a 'del', the
*/
}
-/* appends an item to the list */
+/** appends an item to the list */
void
strListAdd(String * str, const char *item, char del)
{
str->append(item, strlen(item));
}
-/*
+/**
* iterates through a 0-terminated string of items separated by 'del's.
* white space around 'del' is considered to be a part of 'del'
* like strtok, but preserves the source, and can iterate several strings at once
return len > 0;
}
-/* handy to printf prefixes of potentially very long buffers */
+/** handy to printf prefixes of potentially very long buffers */
const char *
getStringPrefix(const char *str, const char *end)
{
return buf;
}
-/*
+/**
* parses an int field, complains if soemthing went wrong, returns true on
* success
*/
}
-/* Parses a quoted-string field (RFC 2616 section 2.2), complains if
+/**
+ * Parses a quoted-string field (RFC 2616 section 2.2), complains if
* something went wrong, returns non-zero on success.
* start should point at the first ".
* RC TODO: This is too looose. We should honour the BNF and exclude CTL's
}
}
-/*
- * httpHdrMangle checks the anonymizer (header_access) configuration.
- * Returns 1 if the header is allowed.
+/**
+ * Checks the anonymizer (header_access) configuration.
+ *
+ * \retval 0 Header is explicitly blocked for removal
+ * \retval 1 Header is explicitly allowed
+ * \retval 1 Header has been replaced, the current version can be used.
+ * \retval 1 Header has no access controls to test
*/
static int
httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, int req_or_rep)
hm = &Config.request_header_access[e->id];
}
+ /* mangler or checklist went away. default allow */
+ if(!hm || !hm->access_list) {
+ return 1;
+ }
+
checklist = aclChecklistCreate(hm->access_list, request, NULL);
- if (1 == checklist->fastCheck()) {
- /* aclCheckFast returns 1 for allow. */
+ if (checklist->fastCheck()) {
+ /* aclCheckFast returns true for allow. */
retval = 1;
} else if (NULL == hm->replacement) {
/* It was denied, and we don't have any replacement */
return retval;
}
-/* Mangles headers for a list of headers. */
+/** Mangles headers for a list of headers. */
void
httpHdrMangleList(HttpHeader * l, HttpRequest * request, int req_or_rep)
{
l->refreshMask();
}
-/*
+/**
* return 1 if manglers are configured. Used to set a flag
* for optimization during request forwarding.
*/
ch.reply = HTTPMSGLOCK(this); // XXX: this lock makes method non-const
ch.request = HTTPMSGLOCK(&request);
for (acl_size_t *l = Config.ReplyBodySize; l; l = l -> next) {
- if (ch.matchAclListFast(l->aclList)) {
+ /* if there is no ACL list or if the ACLs listed match use this size value */
+ if (!l->aclList || ch.matchAclListFast(l->aclList)) {
debugs(58, 4, HERE << "bodySizeMax=" << bodySizeMax);
bodySizeMax = l->size; // may be -1
break;
while (!candidates.empty()) {
if (AccessRule *r = FindRule(topCandidate())) {
+ /* BUG 2526: what to do when r->acl is empty?? */
// XXX: we do not have access to conn->rfc931 here.
acl_checklist = aclChecklistCreate(r->acl, req, dash_str);
acl_checklist->reply = rep ? HTTPMSGLOCK(rep) : NULL;
#if USE_IDENT
- ACLChecklist identChecklist;
-
- identChecklist.src_addr = details->peer;
-
- identChecklist.my_addr = details->me;
-
- identChecklist.accessList = cbdataReference(Config.accessList.identLookup);
-
- /* cbdataReferenceDone() happens in either fastCheck() or ~ACLCheckList */
-
- if (identChecklist.fastCheck())
- identStart(details->me, details->peer, clientIdentDone, connState);
+ if (Config.accessList.identLookup) {
+ ACLChecklist identChecklist;
+ identChecklist.src_addr = details->peer;
+ identChecklist.my_addr = details->me;
+ identChecklist.accessList = cbdataReference(Config.accessList.identLookup);
+ /* cbdataReferenceDone() happens in either fastCheck() or ~ACLCheckList */
+ if (identChecklist.fastCheck())
+ identStart(details->me, details->peer, clientIdentDone, connState);
+ }
#endif
if (s->tcp_keepalive.enabled) {
#if USE_IDENT
- ACLChecklist identChecklist;
+ if (Config.accessList.identLookup) {
+ ACLChecklist identChecklist;
+ identChecklist.src_addr = details->peer;
+ identChecklist.my_addr = details->me;
+ identChecklist.accessList = cbdataReference(Config.accessList.identLookup);
- identChecklist.src_addr = details->peer;
-
- identChecklist.my_addr = details->me;
-
- identChecklist.accessList = cbdataReference(Config.accessList.identLookup);
-
- /* cbdataReferenceDone() happens in either fastCheck() or ~ACLCheckList */
-
- if (identChecklist.fastCheck())
- identStart(details->me, details->peer, clientIdentDone, connState);
+ /* cbdataReferenceDone() happens in either fastCheck() or ~ACLCheckList */
+ if (identChecklist.fastCheck())
+ identStart(details->me, details->peer, clientIdentDone, connState);
+ }
#endif
void
clientReplyContext::processReplyAccess ()
{
+ /* NP: this should probably soft-fail to a zero-sized-reply error ?? */
assert(reply);
- /* Dont't block our own responses or HTTP status messages */
+
+ /** Don't block our own responses or HTTP status messages */
if (http->logType == LOG_TCP_DENIED ||
http->logType == LOG_TCP_DENIED_REPLY ||
alwaysAllowResponse(reply->sline.status)) {
return;
}
+ /** Check for reply to big error */
if (reply->expectedBodyTooLarge(*http->request)) {
sendBodyTooLargeError();
return;
headers_sz = reply->hdr_sz;
+ /** check for absent access controls (permit by default) */
if (!Config.accessList.reply) {
processReplyAccessResult(1);
return;
}
+ /** Process http_reply_access lists */
ACLChecklist *replyChecklist;
replyChecklist = clientAclChecklistCreate(Config.accessList.reply, http);
replyChecklist->reply = HTTPMSGLOCK(reply);
}
#endif /* FOLLOW_X_FORWARDED_FOR */
- acl_checklist = clientAclChecklistCreate(Config.accessList.http, http);
- acl_checklist->nonBlockingCheck(clientAccessCheckDoneWrapper, this);
+ if (Config.accessList.http) {
+ acl_checklist = clientAclChecklistCreate(Config.accessList.http, http);
+ acl_checklist->nonBlockingCheck(clientAccessCheckDoneWrapper, this);
+ } else {
+ debugs(0, DBG_CRITICAL, "No http_access configuration found. This will block ALL traffic");
+ clientAccessCheckDone(ACCESS_DENIED);
+ }
}
void
http->doCallouts();
}
+/** Test cache allow/deny configuration
+ * Sets flags.cachable=1 if caching is not denied.
+ */
void
ClientRequestContext::checkNoCache()
{
- acl_checklist = clientAclChecklistCreate(Config.accessList.noCache, http);
- acl_checklist->nonBlockingCheck(checkNoCacheDoneWrapper, this);
+ if (Config.accessList.noCache) {
+ acl_checklist = clientAclChecklistCreate(Config.accessList.noCache, http);
+ acl_checklist->nonBlockingCheck(checkNoCacheDoneWrapper, this);
+ } else {
+ /* unless otherwise specified, we try to cache. */
+ checkNoCacheDone(1);
+ }
}
static void
STREAM_FAILED
} clientStream_status_t;
-typedef enum {
- ACCESS_DENIED,
- ACCESS_ALLOWED,
- ACCESS_REQ_PROXY_AUTH
-} allow_t;
-
typedef enum {
AUTH_ACL_CHALLENGE = -2,
AUTH_ACL_HELPER = -1,
void
FwdState::fwdStart(int client_fd, StoreEntry *entry, HttpRequest *request)
{
- /*
+ /** \note
* client_addr == no_addr indicates this is an "internal" request
* from peer_digest.c, asn.c, netdb.c, etc and should always
* be allowed. yuck, I know.
*/
- if ( !request->client_addr.IsNoAddr() && request->protocol != PROTO_INTERNAL && request->protocol != PROTO_CACHEOBJ) {
- /*
+ if ( Config.accessList.miss && !request->client_addr.IsNoAddr() &&
+ request->protocol != PROTO_INTERNAL && request->protocol != PROTO_CACHEOBJ) {
+ /**
* Check if this host is allowed to fetch MISSES from us (miss_access)
*/
ACLChecklist ch;
IpAddress addr;
for (l = head; l; l = l->next) {
- if (ch->matchAclListFast(l->aclList))
+ if (!l->aclList || ch->matchAclListFast(l->aclList))
return l->addr;
}
acl_tos *l;
for (l = head; l; l = l->next) {
- if (ch->matchAclListFast(l->aclList))
+ if (!l->aclList || ch->matchAclListFast(l->aclList))
return l->tos;
}
if (request && request->flags.spoof_client_ip)
return request->client_addr;
+ if (!Config.accessList.outgoing_address) {
+ return IpAddress(); // anything will do.
+ }
+
ch.dst_peer = dst_peer;
if (request) {
}
static int
-
htcpAccessCheck(acl_access * acl, htcpSpecifier * s, IpAddress &from)
{
+ /* default deny if no access list present */
+ if (!acl)
+ return 0;
+
ACLChecklist checklist;
checklist.src_addr = from;
checklist.my_addr.SetNoAddr();
int
icpAccessAllowed(IpAddress &from, HttpRequest * icp_request)
{
+ /* absent an explicit allow, we deny all */
+ if (!Config.accessList.icp)
+ return 0;
+
ACLChecklist checklist;
checklist.src_addr = from;
checklist.my_addr.SetNoAddr();
HttpRequest *request = ps->request;
debugs(44, 3, "peerSelectFoo: '" << RequestMethodStr(request->method) << " " << request->GetHost() << "'");
+ /** If we don't known whether DIRECT is permitted ... */
if (ps->direct == DIRECT_UNKNOWN) {
if (ps->always_direct == 0 && Config.accessList.AlwaysDirect) {
+ /** check always_direct; */
ps->acl_checklist = aclChecklistCreate(
Config.accessList.AlwaysDirect,
request,
NULL); /* ident */
- ps->acl_checklist->nonBlockingCheck(peerCheckAlwaysDirectDone,
- ps);
+ ps->acl_checklist->nonBlockingCheck(peerCheckAlwaysDirectDone, ps);
return;
} else if (ps->always_direct > 0) {
+ /** if always_direct says YES, do that. */
ps->direct = DIRECT_YES;
} else if (ps->never_direct == 0 && Config.accessList.NeverDirect) {
+ /** check never_direct; */
ps->acl_checklist = aclChecklistCreate(
Config.accessList.NeverDirect,
request,
ps);
return;
} else if (ps->never_direct > 0) {
+ /** if always_direct says NO, do that. */
ps->direct = DIRECT_NO;
} else if (request->flags.accelerated) {
+ /** if we are accelerating, direct is not an option. */
ps->direct = DIRECT_NO;
} else if (request->flags.loopdetect) {
+ /** if we are in a forwarding-loop, direct is not an option. */
ps->direct = DIRECT_YES;
} else if (peerCheckNetdbDirect(ps)) {
ps->direct = DIRECT_YES;
rq->session.Version = SNMP_VERSION_1;
Community = snmp_parse(&rq->session, PDU, buf, len);
- if (Community) {
+ /* Check if we have explicit permission to access SNMP data.
+ * default (set above) is to deny all */
+ if (Community && Config.accessList.snmp) {
ACLChecklist checklist;
checklist.accessList = cbdataReference(Config.accessList.snmp);
checklist.src_addr = rq->from;
* be allowed. yuck, I know.
*/
- if (!request->client_addr.IsNoAddr()) {
+ if (!request->client_addr.IsNoAddr() && Config.accessList.miss) {
/*
* Check if this host is allowed to fetch MISSES from us (miss_access)
+ * default is to allow.
*/
ACLChecklist ch;
ch.src_addr = request->client_addr;