/* * DEBUG: section 28 Access Control * * 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. * */ #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 "globals.h" #include "profiler/Profiler.h" #include "SquidConfig.h" const ACLFlag ACLFlags::NoFlags[1] = {ACL_F_END}; const char *AclMatchedName = NULL; bool ACLFlags::supported(const ACLFlag f) const { if (f == ACL_F_REGEX_CASE) return true; return (supported_.find(f) != std::string::npos); } void ACLFlags::parseFlags() { char *nextToken; while ((nextToken = ConfigParser::strtokFile()) != NULL && nextToken[0] == '-') { //if token is the "--" break flag if (strcmp(nextToken, "--") == 0) break; for (const char *flg = nextToken+1; *flg!='\0'; flg++ ) { if (supported(*flg)) { makeSet(*flg); } else { debugs(28, 0, HERE << "Flag '" << *flg << "' not supported"); self_destruct(); } } } /*Regex code needs to parse -i file*/ if ( isSet(ACL_F_REGEX_CASE)) ConfigParser::strtokFilePutBack("-i"); if (nextToken != NULL && strcmp(nextToken, "--") != 0 ) ConfigParser::strtokFileUndo(); } 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) { fatal ("unusable ACL::new"); return (void *)1; } void ACL::operator delete (void *address) { fatal ("unusable ACL::delete"); } ACL * ACL::FindByName(const char *name) { ACL *a; debugs(28, 9, "ACL::FindByName '" << name << "'"); for (a = Config.aclList; a; a = a->next) if (!strcasecmp(a->name, name)) return a; debugs(28, 9, "ACL::FindByName found no match"); return NULL; } ACL * ACL::Factory (char const *type) { ACL *result = Prototype::Factory (type); if (!result) fatal ("Unknown acl type in ACL::Factory"); return result; } ACL::ACL() : cfgline(NULL), next(NULL), 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->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 { // have to cast because old match() API is missing const result = const_cast(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) { /* we're already using strtok() to grok the line */ char *t = NULL; ACL *A = NULL; LOCAL_ARRAY(char, aclname, ACL_NAME_SZ); int new_acl = 0; /* snarf the ACL name */ if ((t = strtok(NULL, w_space)) == NULL) { debugs(28, DBG_CRITICAL, "aclParseAclLine: missing ACL name."); parser.destruct(); return; } if (strlen(t) >= ACL_NAME_SZ) { debugs(28, DBG_CRITICAL, "aclParseAclLine: aclParseAclLine: ACL name '" << t << "' too long, max " << ACL_NAME_SZ - 1 << " characters supported"); parser.destruct(); return; } xstrncpy(aclname, t, ACL_NAME_SZ); /* snarf the ACL type */ const char *theType; if ((theType = strtok(NULL, w_space)) == 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) { // Bug 3239: not reliable when there is interception traffic coming 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) { // Bug 3239: not reliable when there is interception traffic coming // Bug 3239: myport - not reliable (yet) when there is interception traffic coming 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."); } if (!Prototype::Registered(theType)) { debugs(28, DBG_CRITICAL, "FATAL: Invalid ACL type '" << theType << "'"); // XXX: make this an ERROR and skip the ACL creation. We *may* die later when its use is attempted. Or may not. parser.destruct(); return; } if ((A = FindByName(aclname)) == NULL) { debugs(28, 3, "aclParseAclLine: Creating ACL '" << aclname << "'"); A = ACL::Factory(theType); A->context(aclname, config_input_line); new_acl = 1; } else { if (strcmp (A->typeString(),theType) ) { debugs(28, DBG_CRITICAL, "aclParseAclLine: ACL '" << A->name << "' already exists with different type."); parser.destruct(); return; } debugs(28, 3, "aclParseAclLine: Appending to '" << aclname << "'"); new_acl = 0; } /* * Here we set AclMatchedName in case we need to use it in a * warning message in aclDomainCompare(). */ AclMatchedName = A->name; /* ugly */ A->flags.parseFlags(); /*split the function here */ A->parse(); /* * Clear AclMatchedName from our temporary hack */ AclMatchedName = NULL; /* ugly */ if (!new_acl) return; if (A->empty()) { debugs(28, DBG_CRITICAL, "Warning: empty ACL: " << A->cfgline); } if (!A->valid()) { fatalf("ERROR: Invalid ACL: %s\n", A->cfgline); } // 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 ACL::isProxyAuth() const { return false; } /* ACL result caching routines */ int ACL::matchForCache(ACLChecklist *checklist) { /* 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 */ } /* * we lookup an acl's cached results, and if we cannot find the acl being * checked we check it and cache the result. This function is a template * method to support caching of multiple acl types. * Note that caching of time based acl's is not * wise in long lived caches (i.e. the auth_user proxy match cache) * RBC * TODO: does a dlink_list perform well enough? Kinkie */ int ACL::cacheMatchAcl(dlink_list * cache, ACLChecklist *checklist) { acl_proxy_auth_match_cache *auth_match; dlink_node *link; link = cache->head; while (link) { auth_match = (acl_proxy_auth_match_cache *)link->data; if (auth_match->acl_data == this) { debugs(28, 4, "ACL::cacheMatchAcl: cache hit on acl '" << name << "' (" << this << ")"); return auth_match->matchrv; } link = link->next; } auth_match = new acl_proxy_auth_match_cache(); auth_match->matchrv = matchForCache (checklist); auth_match->acl_data = 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; } void aclCacheMatchFlush(dlink_list * cache) { acl_proxy_auth_match_cache *auth_match; dlink_node *link, *tmplink; link = cache->head; debugs(28, 8, "aclCacheMatchFlush called for cache " << cache); while (link) { auth_match = (acl_proxy_auth_match_cache *)link->data; tmplink = link; link = link->next; dlinkDelete(tmplink, cache); delete auth_match; } } bool ACL::requiresReply() const { return false; } bool ACL::requiresRequest() const { return false; } /*********************/ /* Destroy functions */ /*********************/ ACL::~ACL() { debugs(28, 3, "ACL::~ACL: '" << cfgline << "'"); safe_free(cfgline); AclMatchedName = NULL; // in case it was pointing to our name } /// Temporary hack to allow old ACL code to handle quoted values without /// replacing every strtok() call. char * ACL::strtok(char *str, const char *delimiters) { return xstrtok(str, delimiters); } ACL::Prototype::Prototype() : prototype (NULL), typeString (NULL) {} ACL::Prototype::Prototype (ACL const *aPrototype, char const *aType) : prototype (aPrototype), typeString (aType) { registerMe (); } Vector * ACL::Prototype::Registry; void *ACL::Prototype::Initialized; bool ACL::Prototype::Registered(char const *aType) { debugs(28, 7, "ACL::Prototype::Registered: invoked for type " << aType); for (iterator i = Registry->begin(); i != Registry->end(); ++i) if (!strcmp (aType, (*i)->typeString)) { debugs(28, 7, "ACL::Prototype::Registered: yes"); return true; } debugs(28, 7, "ACL::Prototype::Registered: no"); return false; } void ACL::Prototype::registerMe () { if (!Registry || (Initialized != ((char *)Registry - 5)) ) { /* TODO: extract this */ /* Not initialised */ Registry = new Vector ; Initialized = (char *)Registry - 5; } if (Registered (typeString)) fatalf ("Attempt to register %s twice", typeString); Registry->push_back (this); } ACL::Prototype::~Prototype() { // TODO: unregister me } ACL * ACL::Prototype::Factory (char const *typeToClone) { 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)) { ACL *A = (*i)->prototype->clone(); A->flags = (*i)->prototype->flags; return A; } debugs(28, 4, "ACL::Prototype::Factory: cloning failed, no type '" << typeToClone << "' available"); return NULL; } void ACL::Initialize() { ACL *a = Config.aclList; debugs(53, 3, "ACL::Initialize"); while (a) { a->prepareForUse(); a = a->next; } }