int IsMatchItemIn(EvalContext *ctx, Item *list, const char *item) /* Solve for possible regex/fuzzy models unified */ { Item *ptr; if ((item == NULL) || (strlen(item) == 0)) { return true; } for (ptr = list; ptr != NULL; ptr = ptr->next) { if (FuzzySetMatch(ptr->name, item) == 0) { return (true); } if (IsRegex(ptr->name)) { if (FullTextMatch(ctx, ptr->name, item)) { return (true); } } } return (false); }
void AbstractFinder::AddSql(String Col, String Pattern, std::stringstream& Sql) { if (Pattern.length() == 0) return; if (IsRegex(Pattern)) { #ifndef WIN32 Sql << " AND " << Col << " regexp ?"; #else for (int i = 0; i < Pattern.length(); i++) { if (Pattern.at(i) == '?') Pattern.at(i) = '_'; else if (Pattern.at(i) == '*') Pattern.at(i) = '%'; } Sql << " AND " << Col << " LIKE ?"; #endif Params.push_back(Pattern); return; } String buf; std::vector<String> tokens; std::vector<String> tokens2; std::stringstream ss(Pattern); while (ss >> buf) tokens.push_back(buf); if (tokens.size() == 0) { Sql << " AND " << Col << " LIKE ?"; Params.push_back("%" + Pattern + "%"); return; } bool b = false; for (int i = 0; i < tokens.size(); i++) { Sql << (i == 0 ? " AND (" : ""); for (int j = 0; j < tokens.at(i).length(); j++) { if (tokens.at(i).at(j) == '+') tokens.at(i).at(j) = ' '; } ss.clear(); ss.str(""); ss << tokens.at(i); tokens2.clear(); while (ss >> buf) tokens2.push_back(buf); if (b && tokens2.size() > 0) Sql << " OR "; if (tokens2.size() > 1) Sql << "("; for (int j = 0; j < tokens2.size(); j++) { if (j != 0) Sql << " AND "; Sql << Col << " LIKE ?"; Params.push_back("%" + tokens2.at(j) + "%"); b = true; } if (tokens2.size() > 1) Sql << ")"; } if (!b) Sql << "0)"; else Sql << ")"; }
int IsPathRegex(char *str) { char *sp; int result = false, s = 0, r = 0; if ((result = IsRegex(str))) { for (sp = str; *sp != '\0'; sp++) { switch (*sp) { case '[': s++; break; case ']': s--; if (s % 2 == 0) { result++; } break; case '(': r++; break; case ')': r--; if (r % 2 == 0) { result++; } break; default: if ((*sp == FILE_SEPARATOR) && (r || s)) { Log(LOG_LEVEL_ERR, "Path regular expression %s seems to use expressions containing the directory symbol %c", str, FILE_SEPARATOR); Log(LOG_LEVEL_ERR, "Use a work-around to avoid pathological behaviour"); return false; } break; } } } return result; }
int IsMatchItemIn(const Item *list, const char *item) /* Solve for possible regex/fuzzy models unified */ { if (item == NULL || item[0] == '\0') { return true; } const Item *ptr = list; CYCLE_DECLARE(ptr, slow, toggle); while (ptr != NULL) { if (FuzzySetMatch(ptr->name, item) == 0 || (IsRegex(ptr->name) && StringMatchFull(ptr->name, item))) { return true; } ptr = ptr->next; CYCLE_CHECK(ptr, slow, toggle); } return false; }
/* Map old-style regex-or-hostname to new-style host-or-domain. * * Old-style ACLs could include regexes to be matched against host * names; but new-style ones only support sub-domain matching. If the * old-style host regex looks like ".*\.sub\.domain\.tld" we can take * it in as ".sub.domain.tld"; otherwise, we can only really map exact * match hostnames. However, we know some old policy (including our * own masterfiles) had cases of .*sub.domain.tld and it's possible * that someone might include a new-style .sub.domain.tld by mistake * in an (old-style) accept list; so cope with these cases, too. * * @param sl The string-list to which to add entries. * @param host The name-or-regex to add to the ACL. * @return An index at which an entry was added to the list (there may * be another), or -1 if nothing added. */ static size_t DeRegexify(StrList **sl, const char *host) { if (IsRegex(host)) { if (host[strcspn(host, "({[|+?]})")] != '\0') { return -1; /* Not a regex we can sensibly massage; discard. */ } bool skip[2] = { false, false }; /* { domain, host } passes below */ const char *name = host; if (name[0] == '^') /* Was always implicit; but read as hint to intent. */ { /* Default to skipping domain-form if anchored explicitly: */ skip[0] = true; /* Over-ridden below if followed by .* of course. */ name++; } if (StringStartsWith(name, ".*")) { skip[0] = false; /* Domain-form should match */ name += 2; } if (StringStartsWith(name, "\\.")) { /* Skip host-form, as the regex definitely wants something * before the given name. */ skip[1] = true; name += 2; } if (strchr(name, '*') != NULL) { /* Can't handle a * later than the preamble. */ return (size_t) -1; } if (name > host || NULL != strchr(host, '\\')) { /* 2: leading '.' and final '\0' */ char copy[2 + strlen(name)], *c = copy; c++[0] = '.'; /* For domain-form; and copy+1 gives host-form. */ /* Now copy the rest of the name, de-regex-ifying as we go: */ for (const char *p = name; p[0] != '\0'; p++) { if (p[0] == '\\') { p++; if (p[0] != '.') { /* Regex includes a non-dot escape */ return (size_t) -1; } } #if 0 else if (p[0] == '.') { /* In principle, this is a special character; but * it may just be an unescaped dot, so let it be. */ } #endif c++[0] = p[0]; } assert(c < copy + sizeof(copy)); c[0] = '\0'; /* Now, for host then domain, add entry if suitable */ int pass = 2; size_t ret = -1; while (pass > 0) { pass--; if (!skip[pass]) /* pass 0 is domain, pass 1 is host */ { ret = StrList_Append(sl, copy + pass); } } return ret; } /* IsRegex() but is actually so boring it's just a name ! */ } /* Just a simple host name. */ return StrList_Append(sl, host); }
void LocateFilePromiserGroup(EvalContext *ctx, char *wildpath, Promise *pp, void (*fnptr) (EvalContext *ctx, char *path, Promise *ptr)) { Item *path, *ip, *remainder = NULL; char pbuffer[CF_BUFSIZE]; struct stat statbuf; int count = 0, lastnode = false, expandregex = false; uid_t agentuid = getuid(); int create = PromiseGetConstraintAsBoolean(ctx, "create", pp); char *pathtype = ConstraintGetRvalValue(ctx, "pathtype", pp, RVAL_TYPE_SCALAR); /* Do a search for promiser objects matching wildpath */ if ((!IsPathRegex(wildpath)) || (pathtype && (strcmp(pathtype, "literal") == 0))) { Log(LOG_LEVEL_VERBOSE, "Using literal pathtype for '%s'", wildpath); (*fnptr) (ctx, wildpath, pp); return; } else { Log(LOG_LEVEL_VERBOSE, "Using regex pathtype for '%s' (see pathtype)", wildpath); } pbuffer[0] = '\0'; path = SplitString(wildpath, '/'); // require forward slash in regex on all platforms for (ip = path; ip != NULL; ip = ip->next) { if ((ip->name == NULL) || (strlen(ip->name) == 0)) { continue; } if (ip->next == NULL) { lastnode = true; } /* No need to chdir as in recursive descent, since we know about the path here */ if (IsRegex(ip->name)) { remainder = ip->next; expandregex = true; break; } else { expandregex = false; } if (!JoinPath(pbuffer, ip->name)) { Log(LOG_LEVEL_ERR, "Buffer has limited size in LocateFilePromiserGroup"); return; } if (stat(pbuffer, &statbuf) != -1) { if ((S_ISDIR(statbuf.st_mode)) && ((statbuf.st_uid) != agentuid) && ((statbuf.st_uid) != 0)) { Log(LOG_LEVEL_INFO, "Directory '%s' in search path '%s' is controlled by another user (uid %ju) - trusting its content is potentially risky (possible race condition)", pbuffer, wildpath, (uintmax_t)statbuf.st_uid); PromiseRef(LOG_LEVEL_INFO, pp); } } } if (expandregex) /* Expand one regex link and hand down */ { char nextbuffer[CF_BUFSIZE], nextbufferOrig[CF_BUFSIZE], regex[CF_BUFSIZE]; const struct dirent *dirp; Dir *dirh; memset(regex, 0, CF_BUFSIZE); strncpy(regex, ip->name, CF_BUFSIZE - 1); if ((dirh = DirOpen(pbuffer)) == NULL) { // Could be a dummy directory to be created so this is not an error. Log(LOG_LEVEL_VERBOSE, "Using best-effort expanded (but non-existent) file base path '%s'", wildpath); (*fnptr) (ctx, wildpath, pp); DeleteItemList(path); return; } else { count = 0; for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh)) { if (!ConsiderLocalFile(dirp->d_name, pbuffer)) { continue; } if ((!lastnode) && (!S_ISDIR(statbuf.st_mode))) { Log(LOG_LEVEL_DEBUG, "Skipping non-directory '%s'", dirp->d_name); continue; } if (FullTextMatch(regex, dirp->d_name)) { Log(LOG_LEVEL_DEBUG, "Link '%s' matched regex '%s'", dirp->d_name, regex); } else { continue; } count++; strncpy(nextbuffer, pbuffer, CF_BUFSIZE - 1); AddSlash(nextbuffer); strcat(nextbuffer, dirp->d_name); for (ip = remainder; ip != NULL; ip = ip->next) { AddSlash(nextbuffer); strcat(nextbuffer, ip->name); } /* The next level might still contain regexs, so go again as long as expansion is not nullpotent */ if ((!lastnode) && (strcmp(nextbuffer, wildpath) != 0)) { LocateFilePromiserGroup(ctx, nextbuffer, pp, fnptr); } else { Promise *pcopy; Log(LOG_LEVEL_VERBOSE, "Using expanded file base path '%s'", nextbuffer); /* Now need to recompute any back references to get the complete path */ snprintf(nextbufferOrig, sizeof(nextbufferOrig), "%s", nextbuffer); MapNameForward(nextbuffer); if (!FullTextMatch(pp->promiser, nextbuffer)) { Log(LOG_LEVEL_DEBUG, "Error recomputing references for '%s' in '%s'", pp->promiser, nextbuffer); } /* If there were back references there could still be match.x vars to expand */ pcopy = ExpandDeRefPromise(ctx, ScopeGetCurrent()->scope, pp); (*fnptr) (ctx, nextbufferOrig, pcopy); PromiseDestroy(pcopy); } } DirClose(dirh); } } else { Log(LOG_LEVEL_VERBOSE, "Using file base path '%s'", pbuffer); (*fnptr) (ctx, pbuffer, pp); } if (count == 0) { Log(LOG_LEVEL_VERBOSE, "No promiser file objects matched as regular expression '%s'", wildpath); if (create) { (*fnptr)(ctx, pp->promiser, pp); } } DeleteItemList(path); }