/* * 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. */ /* * Portions of this code are copyrighted and released under GPLv2+ by: * Copyright (c) 2011, Marcus Kool * Please add new claims to the CONTRIBUTORS file instead. */ /* DEBUG: section 28 Access Control */ #include "squid.h" #include "acl/Acl.h" #include "acl/Checklist.h" #include "acl/RegexData.h" #include "ConfigParser.h" #include "Debug.h" #include "Mem.h" #include "RegexList.h" #include "wordlist.h" static void aclDestroyRegexList(RegexList * data) { RegexList *next = NULL; for (; data; data = next) { next = data->next; regfree(&data->regex); safe_free(data->pattern); memFree(data, MEM_RELIST); } } ACLRegexData::~ACLRegexData() { aclDestroyRegexList(data); } bool ACLRegexData::match(char const *word) { if (word == NULL) return 0; debugs(28, 3, "aclRegexData::match: checking '" << word << "'"); RegexList *first, *prev; first = data; prev = NULL; RegexList *current = first; while (current) { debugs(28, 3, "aclRegexData::match: looking for '" << current->pattern << "'"); if (regexec(¤t->regex, word, 0, 0, 0) == 0) { if (prev != NULL) { /* shift the element just found to the second position * in the list */ prev->next = current->next; current->next = first->next; first->next = current; } debugs(28, 2, "aclRegexData::match: match '" << current->pattern << "' found in '" << word << "'"); return 1; } prev = current; current = current->next; } return 0; } SBufList ACLRegexData::dump() const { SBufList sl; RegexList *temp = data; int flags = REG_EXTENDED | REG_NOSUB; while (temp != NULL) { if (temp->flags != flags) { if ((temp->flags®_ICASE) != 0) { sl.push_back(SBuf("-i")); } else { sl.push_back(SBuf("+i")); } flags = temp->flags; } sl.push_back(SBuf(temp->pattern)); temp = temp->next; } return sl; } static const char * removeUnnecessaryWildcards(char * t) { char * orig = t; if (strncmp(t, "^.*", 3) == 0) t += 3; /* NOTE: an initial '.' might seem unnessary but is not; * it can be a valid requirement that cannot be optimised */ while (*t == '.' && *(t+1) == '*') { t += 2; } if (*t == '\0') { debugs(28, DBG_IMPORTANT, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line); debugs(28, DBG_IMPORTANT, "WARNING: regular expression '" << orig << "' has only wildcards and matches all strings. Using '.*' instead."); return ".*"; } if (t != orig) { debugs(28, DBG_IMPORTANT, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line); debugs(28, DBG_IMPORTANT, "WARNING: regular expression '" << orig << "' has unnecessary wildcard(s). Using '" << t << "' instead."); } return t; } static RegexList ** compileRE(RegexList **Tail, char * RE, int flags) { int errcode; RegexList *q; regex_t comp; if (RE == NULL || *RE == '\0') return Tail; if ((errcode = regcomp(&comp, RE, flags)) != 0) { char errbuf[256]; regerror(errcode, &comp, errbuf, sizeof errbuf); debugs(28, DBG_CRITICAL, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line); debugs(28, DBG_CRITICAL, "ERROR: invalid regular expression: '" << RE << "': " << errbuf); return NULL; } debugs(28, 2, "compileRE: compiled '" << RE << "' with flags " << flags ); q = (RegexList *) memAllocate(MEM_RELIST); q->pattern = xstrdup(RE); q->regex = comp; q->flags = flags; *(Tail) = q; Tail = &q->next; return Tail; } /** Compose and compile one large RE from a set of (small) REs. * The ultimate goal is to have only one RE per ACL so that regexec() is * called only once per ACL. */ static int compileOptimisedREs(RegexList **curlist, wordlist * wl) { RegexList **Tail; RegexList *newlist; RegexList **newlistp; int numREs = 0; int flags = REG_EXTENDED | REG_NOSUB; int largeREindex = 0; char largeRE[BUFSIZ]; newlist = NULL; newlistp = &newlist; largeRE[0] = '\0'; while (wl != NULL) { int RElen; RElen = strlen( wl->key ); if (strcmp(wl->key, "-i") == 0) { if (flags & REG_ICASE) { /* optimisation of -i ... -i */ debugs(28, 2, "compileOptimisedREs: optimisation of -i ... -i" ); } else { debugs(28, 2, "compileOptimisedREs: -i" ); newlistp = compileRE( newlistp, largeRE, flags ); if (newlistp == NULL) { aclDestroyRegexList( newlist ); return 0; } flags |= REG_ICASE; largeRE[largeREindex=0] = '\0'; } } else if (strcmp(wl->key, "+i") == 0) { if ((flags & REG_ICASE) == 0) { /* optimisation of +i ... +i */ debugs(28, 2, "compileOptimisedREs: optimisation of +i ... +i"); } else { debugs(28, 2, "compileOptimisedREs: +i"); newlistp = compileRE( newlistp, largeRE, flags ); if (newlistp == NULL) { aclDestroyRegexList( newlist ); return 0; } flags &= ~REG_ICASE; largeRE[largeREindex=0] = '\0'; } } else if (RElen + largeREindex + 3 < BUFSIZ-1) { debugs(28, 2, "compileOptimisedREs: adding RE '" << wl->key << "'"); if (largeREindex > 0) { largeRE[largeREindex] = '|'; ++largeREindex; } largeRE[largeREindex] = '('; ++largeREindex; for (char * t = wl->key; *t != '\0'; ++t) { largeRE[largeREindex] = *t; ++largeREindex; } largeRE[largeREindex] = ')'; ++largeREindex; largeRE[largeREindex] = '\0'; ++numREs; } else { debugs(28, 2, "compileOptimisedREs: buffer full, generating new optimised RE..." ); newlistp = compileRE( newlistp, largeRE, flags ); if (newlistp == NULL) { aclDestroyRegexList( newlist ); return 0; } largeRE[largeREindex=0] = '\0'; continue; /* do the loop again to add the RE to largeRE */ } wl = wl->next; } newlistp = compileRE( newlistp, largeRE, flags ); if (newlistp == NULL) { aclDestroyRegexList( newlist ); return 0; } /* all was successful, so put the new list at the tail */ if (*curlist == NULL) { *curlist = newlist; } else { for (Tail = curlist; *Tail != NULL; Tail = &((*Tail)->next)) ; (*Tail) = newlist; } debugs(28, 2, "compileOptimisedREs: " << numREs << " REs are optimised into one RE."); if (numREs > 100) { debugs(28, (opt_parse_cfg_only?DBG_IMPORTANT:2), "" << cfg_filename << " line " << config_lineno << ": " << config_input_line); debugs(28, (opt_parse_cfg_only?DBG_IMPORTANT:2), "WARNING: there are more than 100 regular expressions. " << "Consider using less REs or use rules without expressions like 'dstdomain'."); } return 1; } static void compileUnoptimisedREs(RegexList **curlist, wordlist * wl) { RegexList **Tail; RegexList **newTail; int flags = REG_EXTENDED | REG_NOSUB; for (Tail = curlist; *Tail != NULL; Tail = &((*Tail)->next)) ; while (wl != NULL) { if (strcmp(wl->key, "-i") == 0) { flags |= REG_ICASE; } else if (strcmp(wl->key, "+i") == 0) { flags &= ~REG_ICASE; } else { newTail = compileRE( Tail, wl->key , flags ); if (newTail == NULL) debugs(28, DBG_CRITICAL, "ERROR: Skipping regular expression. Compile failed: '" << wl->key << "'"); else Tail = newTail; } wl = wl->next; } } static void aclParseRegexList(RegexList **curlist) { char *t; wordlist *wl = NULL; debugs(28, 2, HERE << "aclParseRegexList: new Regex line or file"); while ((t = ConfigParser::RegexStrtokFile()) != NULL) { const char *clean = removeUnnecessaryWildcards(t); if (strlen(clean) > BUFSIZ-1) { debugs(28, DBG_CRITICAL, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line); debugs(28, DBG_CRITICAL, "ERROR: Skipping regular expression. Larger than " << BUFSIZ-1 << " characters: '" << clean << "'"); } else { debugs(28, 3, "aclParseRegexList: buffering RE '" << clean << "'"); wordlistAdd(&wl, clean); } } if (!compileOptimisedREs(curlist, wl)) { debugs(28, DBG_IMPORTANT, "WARNING: optimisation of regular expressions failed; using fallback method without optimisation"); compileUnoptimisedREs(curlist, wl); } wordlistDestroy(&wl); } void ACLRegexData::parse() { aclParseRegexList(&data); } bool ACLRegexData::empty() const { return data == NULL; } ACLData * ACLRegexData::clone() const { /* Regex's don't clone yet. */ assert (!data); return new ACLRegexData; }