/*
- * DEBUG: section 28 Access Control
- * AUTHOR: Duane Wessels
- *
- * SQUID Web Proxy Cache http://www.squid-cache.org/
- * ----------------------------------------------------------
- *
- * Squid is the result of efforts by numerous individuals from
- * the Internet community; see the CONTRIBUTORS file for full
- * details. Many organizations have provided support for Squid's
- * development; see the SPONSORS file for full details. Squid is
- * Copyrighted (C) 2001 by the Regents of the University of
- * California; see the COPYRIGHT file for full details. Squid
- * incorporates software developed and/or copyrighted by other
- * sources; see the CREDITS file for full details.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ * Copyright (C) 1996-2017 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.
*/
+
+/* DEBUG: section 28 Access Control */
+
#include "squid.h"
#include "acl/Acl.h"
#include "acl/Checklist.h"
+#include "acl/Gadgets.h"
#include "anyp/PortCfg.h"
+#include "cache_cf.h"
#include "ConfigParser.h"
#include "Debug.h"
#include "dlink.h"
+#include "fatal.h"
#include "globals.h"
+#include "profiler/Profiler.h"
#include "SquidConfig.h"
+#include <vector>
+
+#define abortFlags(CONTENT) \
+ do { \
+ debugs(28, 0, CONTENT); \
+ self_destruct(); \
+ } while (0)
+
+const ACLFlag ACLFlags::NoFlags[1] = {ACL_F_END};
+
const char *AclMatchedName = NULL;
+ACLFlags::FlagsTokenizer::FlagsTokenizer(): tokPos(NULL) { }
+
+ACLFlag
+ACLFlags::FlagsTokenizer::nextFlag()
+{
+ if (needNextToken()) {
+ if (!nextToken())
+ return 0;
+ } else
+ ++tokPos;
+ return *tokPos;
+}
+
+bool
+ACLFlags::FlagsTokenizer::hasParameter() const
+{
+ return tokPos && tokPos[0] && tokPos[1] == '=' && tokPos[2];
+}
+
+SBuf
+ACLFlags::FlagsTokenizer::getParameter() const
+{
+ return hasParameter() ? SBuf(&tokPos[2]) : SBuf();
+}
+
+bool
+ACLFlags::FlagsTokenizer::needNextToken() const
+{
+ return !tokPos || !tokPos[0] || !tokPos[1] || tokPos[1] == '=';
+}
+
+bool
+ACLFlags::FlagsTokenizer::nextToken()
+{
+ char *t = ConfigParser::PeekAtToken();
+ if (t == NULL || t[0] != '-' || !t[1])
+ return false;
+ (void)ConfigParser::NextQuotedToken();
+ if (strcmp(t, "--") == 0)
+ return false;
+ tokPos = t + 1;
+ return true;
+}
+
+ACLFlags::~ACLFlags()
+{
+ delete delimiters_;
+}
+
+ACLFlags::Status
+ACLFlags::flagStatus(const ACLFlag f) const
+{
+ if (f == ACL_F_REGEX_CASE)
+ return noParameter;
+ if (f == ACL_F_SUBSTRING)
+ return parameterOptional;
+ if (supported_.find(f) != std::string::npos)
+ return noParameter;
+ return notSupported;
+}
+
+bool
+ACLFlags::parameterSupported(const ACLFlag f, const SBuf &val) const
+{
+ if (f == ACL_F_SUBSTRING)
+ return val.findFirstOf(CharacterSet::ALPHA + CharacterSet::DIGIT) == SBuf::npos;
+ return true;
+}
+
+void
+ACLFlags::makeSet(const ACLFlag f, const SBuf ¶m)
+{
+ flags_ |= flagToInt(f);
+ if (!param.isEmpty())
+ flagParameters_[f].append(param);
+}
+
+void
+ACLFlags::makeUnSet(const ACLFlag f)
+{
+ flags_ &= ~flagToInt(f);
+ flagParameters_[f].clear();
+}
+
+void
+ACLFlags::parseFlags()
+{
+ FlagsTokenizer tokenizer;
+ ACLFlag flag('\0');
+ while ((flag = tokenizer.nextFlag())) {
+ switch (flagStatus(flag))
+ {
+ case notSupported:
+ abortFlags("Flag '" << flag << "' not supported");
+ break;
+ case noParameter:
+ makeSet(flag);
+ break;
+ case parameterRequired:
+ if (!tokenizer.hasParameter()) {
+ abortFlags("Flag '" << flag << "' must have a parameter");
+ break;
+ }
+ case parameterOptional:
+ SBuf param;
+ if (tokenizer.hasParameter()) {
+ param = tokenizer.getParameter();
+ if (!parameterSupported(flag, param))
+ abortFlags("Parameter '" << param << "' for flag '" << flag << "' not supported");
+ }
+ makeSet(flag, param);
+ break;
+ }
+ }
+
+ /*Regex code needs to parse -i file*/
+ if ( isSet(ACL_F_REGEX_CASE)) {
+ ConfigParser::TokenPutBack("-i");
+ makeUnSet('i');
+ }
+}
+
+SBuf
+ACLFlags::parameter(const ACLFlag f) const
+{
+ assert(static_cast<uint32_t>(f - 'A') < FlagIndexMax);
+ auto p = flagParameters_.find(f);
+ return p == flagParameters_.end() ? SBuf() : p->second;
+}
+
+const CharacterSet *
+ACLFlags::delimiters()
+{
+ if (isSet(ACL_F_SUBSTRING) && !delimiters_) {
+ static const SBuf defaultParameter(",");
+ SBuf rawParameter = parameter(ACL_F_SUBSTRING);
+ if (rawParameter.isEmpty())
+ rawParameter = defaultParameter;
+ delimiters_ = new CharacterSet("ACLFlags::delimiters", rawParameter.c_str());
+ }
+ return delimiters_;
+}
+
+const char *
+ACLFlags::flagsStr() const
+{
+ static char buf[64];
+ if (flags_ == 0)
+ return "";
+
+ char *s = buf;
+ *s++ = '-';
+ for (ACLFlag f = 'A'; f <= 'z'; f++) {
+ // ACL_F_REGEX_CASE (-i) flag handled by ACLRegexData class, ignore
+ if (isSet(f) && f != ACL_F_REGEX_CASE)
+ *s++ = f;
+ }
+ *s = '\0';
+ return buf;
+}
+
void *
-ACL::operator new (size_t byteCount)
+ACL::operator new (size_t)
{
fatal ("unusable ACL::new");
return (void *)1;
}
void
-ACL::operator delete (void *address)
+ACL::operator delete (void *)
{
fatal ("unusable ACL::delete");
}
return result;
}
-ACL::ACL () :cfgline(NULL) {}
+ACL::ACL() :
+ cfgline(nullptr),
+ next(nullptr),
+ registered(false)
+{
+ *name = 0;
+}
+
+ACL::ACL(const ACLFlag flgs[]) :
+ cfgline(NULL),
+ next(NULL),
+ flags(flgs),
+ registered(false)
+{
+ *name = 0;
+}
bool ACL::valid () const
{
return true;
}
+bool
+ACL::matches(ACLChecklist *checklist) const
+{
+ PROF_start(ACL_matches);
+ debugs(28, 5, "checking " << name);
+
+ // XXX: AclMatchedName does not contain a matched ACL name when the acl
+ // does not match. It contains the last (usually leaf) ACL name checked
+ // (or is NULL if no ACLs were checked).
+ AclMatchedName = name;
+
+ int result = 0;
+ if (!checklist->hasAle() && requiresAle()) {
+ debugs(28, DBG_IMPORTANT, "WARNING: " << name << " ACL is used in " <<
+ "context without an ALE state. Assuming mismatch.");
+ } else if (!checklist->hasRequest() && requiresRequest()) {
+ debugs(28, DBG_IMPORTANT, "WARNING: " << name << " ACL is used in " <<
+ "context without an HTTP request. Assuming mismatch.");
+ } else if (!checklist->hasReply() && requiresReply()) {
+ debugs(28, DBG_IMPORTANT, "WARNING: " << name << " ACL is used in " <<
+ "context without an HTTP response. Assuming mismatch.");
+ } else {
+ // make sure the ALE has as much data as possible
+ if (requiresAle())
+ checklist->syncAle();
+
+ // have to cast because old match() API is missing const
+ result = const_cast<ACL*>(this)->match(checklist);
+ }
+
+ const char *extra = checklist->asyncInProgress() ? " async" : "";
+ debugs(28, 3, "checked: " << name << " = " << result << extra);
+ PROF_stop(ACL_matches);
+ return result == 1; // true for match; false for everything else
+}
+
+void
+ACL::context(const char *aName, const char *aCfgLine)
+{
+ name[0] = '\0';
+ if (aName)
+ xstrncpy(name, aName, ACL_NAME_SZ-1);
+ safe_free(cfgline);
+ if (aCfgLine)
+ cfgline = xstrdup(aCfgLine);
+}
+
void
ACL::ParseAclLine(ConfigParser &parser, ACL ** head)
{
/* snarf the ACL name */
- if ((t = strtok(NULL, w_space)) == NULL) {
+ if ((t = ConfigParser::NextToken()) == NULL) {
debugs(28, DBG_CRITICAL, "aclParseAclLine: missing ACL name.");
parser.destruct();
return;
/* snarf the ACL type */
const char *theType;
- if ((theType = strtok(NULL, w_space)) == NULL) {
+ if ((theType = ConfigParser::NextToken()) == NULL) {
debugs(28, DBG_CRITICAL, "aclParseAclLine: missing ACL type.");
parser.destruct();
return;
// Is this ACL going to work?
if (strcmp(theType, "myip") == 0) {
- AnyP::PortCfg *p = Config.Sockaddr.http;
- while (p) {
+ AnyP::PortCfgPointer p = HttpPortList;
+ while (p != NULL) {
// Bug 3239: not reliable when there is interception traffic coming
- if (p->intercepted)
+ if (p->flags.natIntercept)
debugs(28, DBG_CRITICAL, "WARNING: 'myip' ACL is not reliable for interception proxies. Please use 'myportname' instead.");
p = p->next;
}
debugs(28, DBG_IMPORTANT, "UPGRADE: ACL 'myip' type is has been renamed to 'localip' and matches the IP the client connected to.");
theType = "localip";
} else if (strcmp(theType, "myport") == 0) {
- AnyP::PortCfg *p = Config.Sockaddr.http;
- while (p) {
+ AnyP::PortCfgPointer p = HttpPortList;
+ while (p != NULL) {
// Bug 3239: not reliable when there is interception traffic coming
// Bug 3239: myport - not reliable (yet) when there is interception traffic coming
- if (p->intercepted)
+ if (p->flags.natIntercept)
debugs(28, DBG_CRITICAL, "WARNING: 'myport' ACL is not reliable for interception proxies. Please use 'myportname' instead.");
p = p->next;
}
theType = "localport";
debugs(28, DBG_IMPORTANT, "UPGRADE: ACL 'myport' type is has been renamed to 'localport' and matches the port the client connected to.");
+ } else if (strcmp(theType, "proto") == 0 && strcmp(aclname, "manager") == 0) {
+ // ACL manager is now a built-in and has a different type.
+ debugs(28, DBG_PARSE_NOTE(DBG_IMPORTANT), "UPGRADE: ACL 'manager' is now a built-in ACL. Remove it from your config file.");
+ return; // ignore the line
}
if (!Prototype::Registered(theType)) {
if ((A = FindByName(aclname)) == NULL) {
debugs(28, 3, "aclParseAclLine: Creating ACL '" << aclname << "'");
A = ACL::Factory(theType);
- xstrncpy(A->name, aclname, ACL_NAME_SZ);
- A->cfgline = xstrdup(config_input_line);
+ A->context(aclname, config_input_line);
new_acl = 1;
} else {
if (strcmp (A->typeString(),theType) ) {
* Here we set AclMatchedName in case we need to use it in a
* warning message in aclDomainCompare().
*/
- AclMatchedName = A->name; /* ugly */
+ AclMatchedName = A->name; /* ugly */
+
+ A->flags.parseFlags();
/*split the function here */
A->parse();
/*
* Clear AclMatchedName from our temporary hack
*/
- AclMatchedName = NULL; /* ugly */
+ AclMatchedName = NULL; /* ugly */
if (!new_acl)
return;
A->cfgline);
}
- /* append */
- while (*head)
- head = &(*head)->next;
-
+ // add to the global list for searching explicit ACLs by name
+ assert(head && *head == Config.aclList);
+ A->next = *head;
*head = A;
+
+ // register for centralized cleanup
+ aclRegister(A);
}
bool
return false;
}
-ACLList::ACLList() : op (1), _acl (NULL), next (NULL)
-{}
-
-void
-ACLList::negated(bool isNegated)
-{
- if (isNegated)
- op = 0;
- else
- op = 1;
-}
-
/* ACL result caching routines */
int
-ACL::matchForCache(ACLChecklist *checklist)
+ACL::matchForCache(ACLChecklist *)
{
/* This is a fatal to ensure that cacheMatchAcl calls are _only_
* made for supported acl types */
fatal("aclCacheMatchAcl: unknown or unexpected ACL type");
- return 0; /* NOTREACHED */
+ return 0; /* NOTREACHED */
}
/*
link = link->next;
}
- auth_match = new acl_proxy_auth_match_cache();
- auth_match->matchrv = matchForCache (checklist);
- auth_match->acl_data = this;
+ auth_match = new acl_proxy_auth_match_cache(matchForCache(checklist), this);
dlinkAddTail(auth_match, &auth_match->link, cache);
debugs(28, 4, "ACL::cacheMatchAcl: miss for '" << name << "'. Adding result " << auth_match->matchrv);
return auth_match->matchrv;
}
bool
-ACL::requiresReply() const
+ACL::requiresAle() const
{
return false;
}
bool
-ACL::requiresRequest() const
+ACL::requiresReply() const
{
return false;
}
-int
-ACL::checklistMatches(ACLChecklist *checklist)
-{
- int rv;
-
- if (!checklist->hasRequest() && requiresRequest()) {
- debugs(28, DBG_IMPORTANT, "ACL::checklistMatches WARNING: '" << name << "' ACL is used but there is no HTTP request -- not matching.");
- return 0;
- }
-
- if (!checklist->hasReply() && requiresReply()) {
- debugs(28, DBG_IMPORTANT, "ACL::checklistMatches WARNING: '" << name << "' ACL is used but there is no HTTP reply -- not matching.");
- return 0;
- }
-
- debugs(28, 3, "ACL::checklistMatches: checking '" << name << "'");
- rv= match(checklist);
- debugs(28, 3, "ACL::ChecklistMatches: result for '" << name << "' is " << rv);
- return rv;
-}
-
bool
-ACLList::matches (ACLChecklist *checklist) const
+ACL::requiresRequest() const
{
- assert (_acl);
- // XXX: AclMatchedName does not contain a matched ACL name when the acl
- // does not match (or contains stale name if no ACLs are checked). In
- // either case, we get misleading debugging and possibly incorrect error
- // messages. Unfortunately, deny_info's "when none http_access
- // lines match" exception essentially requires this mess.
- // TODO: Rework by using an acl-free deny_info for the no-match cases?
- AclMatchedName = _acl->name;
- debugs(28, 3, "ACLList::matches: checking " << (op ? null_string : "!") << _acl->name);
-
- if (_acl->checklistMatches(checklist) != op) {
- debugs(28, 4, "ACLList::matches: result is false");
- return false;
- }
-
- debugs(28, 4, "ACLList::matches: result is true");
- return true;
+ return false;
}
/*********************/
ACL::~ACL()
{
- debugs(28, 3, "ACL::~ACL: '" << cfgline << "'");
+ debugs(28, 3, "freeing ACL " << name);
safe_free(cfgline);
-}
-
-/* to be split into separate files in the future */
-
-CBDATA_CLASS_INIT(acl_access);
-
-void *
-acl_access::operator new (size_t)
-{
- CBDATA_INIT_TYPE(acl_access);
- acl_access *result = cbdataAlloc(acl_access);
- return result;
-}
-
-void
-acl_access::operator delete (void *address)
-{
- acl_access *t = static_cast<acl_access *>(address);
- cbdataFree(t);
+ AclMatchedName = NULL; // in case it was pointing to our name
}
ACL::Prototype::Prototype() : prototype (NULL), typeString (NULL) {}
registerMe ();
}
-Vector<ACL::Prototype const *> * ACL::Prototype::Registry;
+std::vector<ACL::Prototype const *> * ACL::Prototype::Registry;
void *ACL::Prototype::Initialized;
bool
if (!Registry || (Initialized != ((char *)Registry - 5)) ) {
/* TODO: extract this */
/* Not initialised */
- Registry = new Vector <ACL::Prototype const *>;
+ Registry = new std::vector<ACL::Prototype const *>;
Initialized = (char *)Registry - 5;
}
debugs(28, 4, "ACL::Prototype::Factory: cloning an object for type '" << typeToClone << "'");
for (iterator i = Registry->begin(); i != Registry->end(); ++i)
- if (!strcmp (typeToClone, (*i)->typeString))
- return (*i)->prototype->clone();
+ if (!strcmp (typeToClone, (*i)->typeString)) {
+ ACL *A = (*i)->prototype->clone();
+ A->flags = (*i)->prototype->flags;
+ return A;
+ }
debugs(28, 4, "ACL::Prototype::Factory: cloning failed, no type '" << typeToClone << "' available");
a = a->next;
}
}
+