static void test_match_full(void) { assert_true(StringMatchFull("^a.*$", "abc")); assert_true(StringMatchFull("a", "a")); assert_false(StringMatchFull("a", "ab")); assert_false(StringMatchFull("^a.*$", "bac")); }
int IsRegexItemIn(EvalContext *ctx, Item *list, char *regex) { Item *ptr; for (ptr = list; ptr != NULL; ptr = ptr->next) { if ((ptr->classes) && (!IsDefinedClass(ctx, ptr->classes))) { continue; } /* Avoid using regex if possible, due to memory leak */ if (strcmp(regex, ptr->name) == 0) { return true; } /* Make it commutative */ if ((StringMatchFull(regex, ptr->name)) || (StringMatchFull(ptr->name, regex))) { return true; } } return false; }
int IsRegexItemIn(const EvalContext *ctx, const Item *list, const char *regex) { for (const Item *ptr = list; ptr != NULL; ptr = ptr->next) { if (ctx != NULL && ptr->classes != NULL && !IsDefinedClass(ctx, ptr->classes)) { continue; } /* Cheap pre-test: */ if (strcmp(regex, ptr->name) == 0) { return true; } /* Make it commutative */ if (StringMatchFull(regex, ptr->name) || StringMatchFull(ptr->name, regex)) { return true; } } return false; }
static bool SelectProcRegexMatch(const char *name1, const char *name2, const char *regex, bool anchored, char **colNames, char **line) { int i; if (regex == NULL) { return false; } if ((i = GetProcColumnIndex(name1, name2, colNames)) != -1) { if (anchored) { return StringMatchFull(regex, line[i]); } else { int s, e; return StringMatch(regex, line[i], &s, &e); } } return false; }
static bool SelectProcRegexMatch(const char *name1, const char *name2, const char *regex, char **colNames, char **line) { int i; if (regex == NULL) { return false; } if ((i = GetProcColumnIndex(name1, name2, colNames)) != -1) { if (StringMatchFull(regex, line[i])) { return true; } else { return false; } } return false; }
static SyntaxTypeMatch CheckParseString(const char *lval, const char *s, const char *range) { if (s == NULL) { return SYNTAX_TYPE_MATCH_OK; } if (strlen(range) == 0) { return SYNTAX_TYPE_MATCH_OK; } if (IsNakedVar(s, '@') || IsNakedVar(s, '$')) { return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; } /* Deal with complex strings as special cases */ if (strcmp(lval, "mode") == 0 || strcmp(lval, "search_mode") == 0) { mode_t plus, minus; if (!ParseModeString(s, &plus, &minus)) { return SYNTAX_TYPE_MATCH_ERROR_STRING_UNIX_PERMISSION; } } /* FIXME: review this strcmp. Moved out from StringMatch */ if (!strcmp(range, s) || StringMatchFull(range, s)) { return SYNTAX_TYPE_MATCH_OK; } if (IsCf3VarString(s)) { return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; } else if ('\0' == s[0]) { return SYNTAX_TYPE_MATCH_ERROR_EMPTY_SCALAR_OUT_OF_RANGE; } else if (!strcmp(range, CF_ABSPATHRANGE)) { return SYNTAX_TYPE_MATCH_ERROR_ABSOLUTE_PATH; } else { return SYNTAX_TYPE_MATCH_ERROR_SCALAR_OUT_OF_RANGE; } return SYNTAX_TYPE_MATCH_OK; }
SyntaxTypeMatch CheckParseContext(const char *context, const char *range) { if (strlen(range) == 0) { return SYNTAX_TYPE_MATCH_OK; } if (StringMatchFull(range, context)) { return SYNTAX_TYPE_MATCH_OK; } return SYNTAX_TYPE_MATCH_ERROR_CONTEXT_OUT_OF_RANGE; }
SyntaxTypeMatch CheckParseContext(const char *context, const char *range) { if (strlen(range) == 0) { return SYNTAX_TYPE_MATCH_OK; } /* FIXME: review this strcmp. Moved out from StringMatch */ if (!strcmp(range, context) || StringMatchFull(range, context)) { return SYNTAX_TYPE_MATCH_OK; } return SYNTAX_TYPE_MATCH_ERROR_CONTEXT_OUT_OF_RANGE; }
static SyntaxTypeMatch CheckParseString(const char *lval, const char *s, const char *range) { if (s == NULL) { return SYNTAX_TYPE_MATCH_OK; } if (strlen(range) == 0) { return SYNTAX_TYPE_MATCH_OK; } if (IsNakedVar(s, '@') || IsNakedVar(s, '$')) { return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; } /* Deal with complex strings as special cases */ if (strcmp(lval, "mode") == 0 || strcmp(lval, "search_mode") == 0) { mode_t plus, minus; if (!ParseModeString(s, &plus, &minus)) { return SYNTAX_TYPE_MATCH_ERROR_STRING_UNIX_PERMISSION; } } if (StringMatchFull(range, s)) { return SYNTAX_TYPE_MATCH_OK; } if (IsCf3VarString(s)) { return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED; } else { return SYNTAX_TYPE_MATCH_ERROR_SCALAR_OUT_OF_RANGE; } return SYNTAX_TYPE_MATCH_OK; }
bool CompareStringOrRegex(const char *value, const char *compareTo, bool regex) { if (regex) { if (!NULL_OR_EMPTY(compareTo) && !StringMatchFull(compareTo, value)) { return false; } } else { if (!NULL_OR_EMPTY(compareTo) && strcmp(compareTo, value) != 0) { return false; } } return true; }
bool RlistIsInListOfRegex(const Rlist *list, const char *str) /* Returns true if any of the "list" of regular expressions matches "str". Non-scalars in "list" are skipped. */ { if (str == NULL || list == NULL) { return false; } for (const Rlist *rp = list; rp != NULL; rp = rp->next) { if (rp->val.type == RVAL_TYPE_SCALAR && StringMatchFull(RlistScalarValue(rp), str)) { return true; } } return false; }
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; }
/** * Currently this function returns false when we want the connection * closed, and true, when we want to proceed further with requests. * * @TODO So we need this function to return more than true/false, because now * we return true even when access is denied! E.g. return -1 for error, 0 on * success, 1 on access denied. It can be an option if connection will close * on denial. */ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn) { /* The CF_BUFEXT extra space is there to ensure we're not *reading* out of * bounds in commands that carry extra binary arguments, like MD5. */ char recvbuffer[CF_BUFSIZE + CF_BUFEXT] = { 0 }; /* This size is the max we can SendTransaction(). */ char sendbuffer[CF_BUFSIZE - CF_INBAND_OFFSET] = { 0 }; char filename[CF_BUFSIZE + 1]; /* +1 for appending slash sometimes */ ServerFileGetState get_args = { 0 }; /* We already encrypt because of the TLS layer, no need to encrypt more. */ const int encrypted = 0; /* Legacy stuff only for old protocol. */ assert(conn->rsa_auth == 1); assert(conn->user_data_set == 1); /* Receive up to CF_BUFSIZE - 1 bytes. */ const int received = ReceiveTransaction(conn->conn_info, recvbuffer, NULL); if (received == -1) { /* Already Log()ged in case of error. */ return false; } if (received > CF_BUFSIZE - 1) { UnexpectedError("Received transaction of size %d", received); return false; } if (strlen(recvbuffer) == 0) { Log(LOG_LEVEL_WARNING, "Got NULL transmission (of size %d)", received); return true; } /* Don't process request if we're signalled to exit. */ if (IsPendingTermination()) { Log(LOG_LEVEL_VERBOSE, "Server must exit, closing connection"); return false; } /* TODO break recvbuffer here: command, param1, param2 etc. */ switch (GetCommandNew(recvbuffer)) { case PROTOCOL_COMMAND_EXEC: { const size_t EXEC_len = strlen(PROTOCOL_NEW[PROTOCOL_COMMAND_EXEC]); /* Assert recvbuffer starts with EXEC. */ assert(strncmp(PROTOCOL_NEW[PROTOCOL_COMMAND_EXEC], recvbuffer, EXEC_len) == 0); char *args = &recvbuffer[EXEC_len]; args += strspn(args, " \t"); /* bypass spaces */ Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "EXEC", args); bool b = DoExec2(ctx, conn, args, sendbuffer, sizeof(sendbuffer)); /* In the end we might keep the connection open (return true) to be * ready for next requests, but we must always send the TERMINATOR * string so that the client can close the connection at will. */ Terminate(conn->conn_info); return b; } case PROTOCOL_COMMAND_VERSION: snprintf(sendbuffer, sizeof(sendbuffer), "OK: %s", Version()); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; case PROTOCOL_COMMAND_GET: { int ret = sscanf(recvbuffer, "GET %d %[^\n]", &(get_args.buf_size), filename); if (ret != 2 || get_args.buf_size <= 0 || get_args.buf_size > CF_BUFSIZE) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "GET", filename); /* TODO batch all the following in one function since it's very * similar in all of GET, OPENDIR and STAT. */ size_t zret = ShortcutsExpand(filename, sizeof(filename), SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename)); if (zret == (size_t) -1) { RefuseAccess(conn, recvbuffer); return true; } PathRemoveTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "GET", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to GET: %s", filename); RefuseAccess(conn, recvbuffer); return true; } memset(sendbuffer, 0, sizeof(sendbuffer)); if (get_args.buf_size >= CF_BUFSIZE) { get_args.buf_size = 2048; } /* TODO eliminate! */ get_args.conn = conn; get_args.encrypt = false; get_args.replybuff = sendbuffer; get_args.replyfile = filename; CfGetFile(&get_args); return true; } case PROTOCOL_COMMAND_OPENDIR: { memset(filename, 0, sizeof(filename)); int ret = sscanf(recvbuffer, "OPENDIR %[^\n]", filename); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "OPENDIR", filename); /* sizeof()-1 because we need one extra byte for appending '/' afterwards. */ size_t zret = ShortcutsExpand(filename, sizeof(filename) - 1, SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename) - 1); if (zret == (size_t) -1) { RefuseAccess(conn, recvbuffer); return true; } /* OPENDIR *must* be directory. */ PathAppendTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "OPENDIR", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to OPENDIR: %s", filename); RefuseAccess(conn, recvbuffer); return true; } CfOpenDirectory(conn, sendbuffer, filename); return true; } case PROTOCOL_COMMAND_SYNCH: { long time_no_see = 0; memset(filename, 0, sizeof(filename)); int ret = sscanf(recvbuffer, "SYNCH %ld STAT %[^\n]", &time_no_see, filename); if (ret != 2 || filename[0] == '\0') { goto protocol_error; } time_t tloc = time(NULL); if (tloc == -1) { /* Should never happen. */ Log(LOG_LEVEL_ERR, "Couldn't read system clock. (time: %s)", GetErrorStr()); SendTransaction(conn->conn_info, "BAD: clocks out of synch", 0, CF_DONE); return true; } time_t trem = (time_t) time_no_see; int drift = (int) (tloc - trem); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "STAT", filename); /* sizeof()-1 because we need one extra byte for appending '/' afterwards. */ size_t zret = ShortcutsExpand(filename, sizeof(filename) - 1, SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename) - 1); if (zret == (size_t) -1) { RefuseAccess(conn, recvbuffer); return true; } if (IsDirReal(filename) == 1) { PathAppendTrailingSlash(filename, strlen(filename)); } else { PathRemoveTrailingSlash(filename, strlen(filename)); } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "STAT", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to STAT: %s", filename); RefuseAccess(conn, recvbuffer); return true; } Log(LOG_LEVEL_DEBUG, "Clocks were off by %ld", (long) tloc - (long) trem); if (DENYBADCLOCKS && (drift * drift > CLOCK_DRIFT * CLOCK_DRIFT)) { snprintf(sendbuffer, sizeof(sendbuffer), "BAD: Clocks are too far unsynchronized %ld/%ld", (long) tloc, (long) trem); Log(LOG_LEVEL_INFO, "denybadclocks %s", sendbuffer); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; } StatFile(conn, sendbuffer, filename); return true; } case PROTOCOL_COMMAND_MD5: { int ret = sscanf(recvbuffer, "MD5 %[^\n]", filename); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "MD5", filename); /* TODO batch all the following in one function since it's very * similar in all of GET, OPENDIR and STAT. */ size_t zret = ShortcutsExpand(filename, sizeof(filename), SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename)); if (zret == (size_t) -1) { RefuseAccess(conn, recvbuffer); return true; } PathRemoveTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "MD5", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to file: %s", filename); RefuseAccess(conn, recvbuffer); return true; } assert(CF_DEFAULT_DIGEST_LEN <= EVP_MAX_MD_SIZE); unsigned char digest[EVP_MAX_MD_SIZE + 1]; assert(CF_BUFSIZE + CF_SMALL_OFFSET + CF_DEFAULT_DIGEST_LEN <= sizeof(recvbuffer)); memcpy(digest, recvbuffer + strlen(recvbuffer) + CF_SMALL_OFFSET, CF_DEFAULT_DIGEST_LEN); CompareLocalHash(filename, digest, sendbuffer); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; } case PROTOCOL_COMMAND_VAR: { char var[256]; int ret = sscanf(recvbuffer, "VAR %255[^\n]", var); if (ret != 1) { goto protocol_error; } /* TODO if this is literals_acl, then when should I check vars_acl? */ if (acl_CheckExact(literals_acl, var, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to variable: %s", var); RefuseAccess(conn, recvbuffer); return true; } GetServerLiteral(ctx, conn, sendbuffer, recvbuffer, encrypted); return true; } case PROTOCOL_COMMAND_CONTEXT: { char client_regex[256]; int ret = sscanf(recvbuffer, "CONTEXT %255[^\n]", client_regex); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "CONTEXT", client_regex); /* WARNING: this comes from legacy code and must be killed if we care * about performance. We should not accept regular expressions from * the client, but this will break backwards compatibility. * * I replicated the code in raw form here to emphasize complexity, * it's the only *slow* command currently in the protocol. */ Item *persistent_classes = ListPersistentClasses(); Item *matched_classes = NULL; /* For all persistent classes */ for (Item *ip = persistent_classes; ip != NULL; ip = ip->next) { const char *class_name = ip->name; /* Does this class match the regex the client sent? */ if (StringMatchFull(client_regex, class_name)) { /* Is this class allowed to be given to the specific * host, according to the regexes in the ACLs? */ if (acl_CheckRegex(classes_acl, class_name, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info)), NULL) == true) { Log(LOG_LEVEL_DEBUG, "Access granted to class: %s", class_name); PrependItem(&matched_classes, class_name, NULL); } } } if (matched_classes == NULL) { Log(LOG_LEVEL_INFO, "No allowed classes for remoteclassesmatching: %s", client_regex); RefuseAccess(conn, recvbuffer); return true; } ReplyServerContext(conn, encrypted, matched_classes); return true; } case PROTOCOL_COMMAND_QUERY: { char query[256], name[128]; int ret1 = sscanf(recvbuffer, "QUERY %255[^\n]", query); int ret2 = sscanf(recvbuffer, "QUERY %127s", name); if (ret1 != 1 || ret2 != 1) { goto protocol_error; } if (acl_CheckExact(query_acl, name, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to query: %s", query); RefuseAccess(conn, recvbuffer); return true; } if (GetServerQuery(conn, recvbuffer, encrypted)) { return true; } break; } case PROTOCOL_COMMAND_CALL_ME_BACK: /* Server side, handing the collect call off to cf-hub. */ if (acl_CheckExact(query_acl, "collect_calls", conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to Call-Collect, check the ACL for class: collect_calls"); return false; } ReceiveCollectCall(conn); /* On success that returned true; otherwise, it did all * relevant Log()ging. Either way, we're no longer busy with * it and our caller can close the connection: */ return false; case PROTOCOL_COMMAND_BAD: Log(LOG_LEVEL_WARNING, "Unexpected protocol command: %s", recvbuffer); } /* We should only reach this point if something went really bad, and * close connection. In all other cases (like access denied) connection * shouldn't be closed. */ protocol_error: strcpy(sendbuffer, "BAD: Request denied"); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); Log(LOG_LEVEL_INFO, "Closing connection due to illegal request: %s", recvbuffer); return false; }
PromiseResult VerifyClassPromise(EvalContext *ctx, const Promise *pp, ARG_UNUSED void *param) { assert(param == NULL); Attributes a = GetClassContextAttributes(ctx, pp); if (!StringMatchFull("[a-zA-Z0-9_]+", pp->promiser)) { Log(LOG_LEVEL_VERBOSE, "Class identifier '%s' contains illegal characters - canonifying", pp->promiser); xsnprintf(pp->promiser, strlen(pp->promiser) + 1, "%s", CanonifyName(pp->promiser)); } if (a.context.nconstraints == 0) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "No constraints for class promise '%s'", pp->promiser); return PROMISE_RESULT_FAIL; } if (a.context.nconstraints > 1) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Irreconcilable constraints in classes for '%s'", pp->promiser); return PROMISE_RESULT_FAIL; } if (EvalClassExpression(ctx, a.context.expression, pp)) { if (!ValidClassName(pp->promiser)) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Attempted to name a class '%s', which is an illegal class identifier", pp->promiser); return PROMISE_RESULT_FAIL; } else { char *tags = NULL; { Buffer *tag_buffer = BufferNew(); BufferAppendString(tag_buffer, "classes promise,attribute_name=label,source=promise"); for (const Rlist *rp = PromiseGetConstraintAsList(ctx, "meta", pp); rp; rp = rp->next) { BufferAppendChar(tag_buffer, ','); BufferAppendString(tag_buffer, RlistScalarValue(rp)); } tags = BufferClose(tag_buffer); } if (/* Persistent classes are always global: */ a.context.persistent > 0 || /* Namespace-scope is global: */ a.context.scope == CONTEXT_SCOPE_NAMESPACE || /* If there is no explicit scope, common bundles define global * classes, other bundles define local classes: */ (a.context.scope == CONTEXT_SCOPE_NONE && 0 == strcmp(PromiseGetBundle(pp)->type, "common"))) { Log(LOG_LEVEL_VERBOSE, "C: + Global class: %s ", pp->promiser); EvalContextClassPutSoft(ctx, pp->promiser, CONTEXT_SCOPE_NAMESPACE, tags); } else { Log(LOG_LEVEL_VERBOSE, "C: + Private class: %s ", pp->promiser); EvalContextClassPutSoft(ctx, pp->promiser, CONTEXT_SCOPE_BUNDLE, tags); } if (a.context.persistent > 0) { Log(LOG_LEVEL_VERBOSE, "C: + Persistent class: '%s'. (%d minutes)", pp->promiser, a.context.persistent); EvalContextHeapPersistentSave(ctx, pp->promiser, a.context.persistent, CONTEXT_STATE_POLICY_RESET, tags); } free(tags); return PROMISE_RESULT_NOOP; } } return PROMISE_RESULT_NOOP; }
static void MonLogSymbolicValue(EvalContext *ctx, const char *handle, Item *stream, Attributes a, const Promise *pp, PromiseResult *result) { char value[CF_BUFSIZE], sdate[CF_MAXVARSIZE], filename[CF_BUFSIZE], *v; int count = 1, found = false, match_count = 0; Item *ip, *match = NULL, *matches = NULL; time_t now = time(NULL); FILE *fout; if (stream == NULL) { Log(LOG_LEVEL_VERBOSE, "No stream to measure"); return; } Log(LOG_LEVEL_VERBOSE, "Locate and log sample ..."); for (ip = stream; ip != NULL; ip = ip->next) { if (ip->name == NULL) { continue; } if (count == a.measure.select_line_number) { Log(LOG_LEVEL_VERBOSE, "Found line %d by number...", count); found = true; match_count = 1; match = ip; if (a.measure.extraction_regex) { Log(LOG_LEVEL_VERBOSE, "Now looking for a matching extractor \"%s\"", a.measure.extraction_regex); strncpy(value, ExtractFirstReference(a.measure.extraction_regex, match->name), CF_MAXVARSIZE - 1); Log(LOG_LEVEL_INFO, "Extracted value \"%s\" for promise \"%s\"", value, handle); AppendItem(&matches, value, NULL); } break; } if (a.measure.select_line_matching && StringMatchFull(a.measure.select_line_matching, ip->name)) { Log(LOG_LEVEL_VERBOSE, "Found line %d by pattern...", count); found = true; match = ip; match_count++; if (a.measure.extraction_regex) { Log(LOG_LEVEL_VERBOSE, "Now looking for a matching extractor \"%s\"", a.measure.extraction_regex); strncpy(value, ExtractFirstReference(a.measure.extraction_regex, match->name), CF_MAXVARSIZE - 1); Log(LOG_LEVEL_INFO, "Extracted value \"%s\" for promise \"%s\"", value, handle); AppendItem(&matches, value, NULL); } } count++; } if (!found) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Promiser '%s' found no matching line.", pp->promiser); *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); return; } if (match_count > 1) { Log(LOG_LEVEL_INFO, "Warning: %d lines matched the line_selection \"%s\"- matching to last", match_count, a.measure.select_line_matching); } switch (a.measure.data_type) { case CF_DATA_TYPE_COUNTER: Log(LOG_LEVEL_VERBOSE, "Counted %d for %s", match_count, handle); snprintf(value, CF_MAXVARSIZE, "%d", match_count); break; case CF_DATA_TYPE_STRING_LIST: v = ItemList2CSV(matches); snprintf(value, CF_BUFSIZE, "%s", v); free(v); break; default: snprintf(value, CF_BUFSIZE, "%s", matches->name); } DeleteItemList(matches); if (a.measure.history_type && strcmp(a.measure.history_type, "log") == 0) { snprintf(filename, CF_BUFSIZE, "%s%cstate%c%s_measure.log", CFWORKDIR, FILE_SEPARATOR, FILE_SEPARATOR, handle); if ((fout = fopen(filename, "a")) == NULL) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Unable to open the output log \"%s\"", filename); *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); PromiseRef(LOG_LEVEL_ERR, pp); return; } strncpy(sdate, ctime(&now), CF_MAXVARSIZE - 1); if (Chop(sdate, CF_EXPANDSIZE) == -1) { Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator"); } fprintf(fout, "%s,%ld,%s\n", sdate, (long) now, value); Log(LOG_LEVEL_VERBOSE, "Logging: %s,%s to %s", sdate, value, filename); fclose(fout); } else // scalar or static { CF_DB *dbp; char id[CF_MAXVARSIZE]; if (!OpenDB(&dbp, dbid_static)) { return; } snprintf(id, CF_MAXVARSIZE - 1, "%s:%d", handle, a.measure.data_type); WriteDB(dbp, id, value, strlen(value) + 1); CloseDB(dbp); } }
int MatchClasses(EvalContext *ctx, ServerConnectionState *conn) { char recvbuffer[CF_BUFSIZE]; Item *classlist = NULL, *ip; int count = 0; while (true && (count < 10)) /* arbitrary check to avoid infinite loop, DoS attack */ { count++; if (ReceiveTransaction(&conn->conn_info, recvbuffer, NULL) == -1) { Log(LOG_LEVEL_VERBOSE, "Unable to read data from network. (ReceiveTransaction: %s)", GetErrorStr()); return false; } Log(LOG_LEVEL_DEBUG, "Got class buffer '%s'", recvbuffer); if (strncmp(recvbuffer, CFD_TERMINATOR, strlen(CFD_TERMINATOR)) == 0) { if (count == 1) { Log(LOG_LEVEL_DEBUG, "No classes were sent, assuming no restrictions..."); return true; } break; } classlist = SplitStringAsItemList(recvbuffer, ' '); for (ip = classlist; ip != NULL; ip = ip->next) { Log(LOG_LEVEL_VERBOSE, "Checking whether class %s can be identified as me...", ip->name); if (IsDefinedClass(ctx, ip->name, NULL)) { Log(LOG_LEVEL_DEBUG, "Class '%s' matched, accepting...", ip->name); DeleteItemList(classlist); return true; } { ClassTableIterator *iter = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true); Class *cls = NULL; while ((cls = ClassTableIteratorNext(iter))) { char *expr = ClassRefToString(cls->ns, cls->name); bool match = StringMatchFull(ip->name, expr); free(expr); if (match) { Log(LOG_LEVEL_DEBUG, "Class matched regular expression '%s', accepting...", ip->name); DeleteItemList(classlist); return true; } } ClassTableIteratorDestroy(iter); } if (strncmp(ip->name, CFD_TERMINATOR, strlen(CFD_TERMINATOR)) == 0) { Log(LOG_LEVEL_VERBOSE, "No classes matched, rejecting...."); ReplyNothing(conn); DeleteItemList(classlist); return false; } } } ReplyNothing(conn); Log(LOG_LEVEL_VERBOSE, "No classes matched, rejecting...."); DeleteItemList(classlist); return false; }
PromiseResult VerifyVarPromise(EvalContext *ctx, const Promise *pp, bool allow_duplicates) { ConvergeVariableOptions opts = CollectConvergeVariableOptions(ctx, pp, allow_duplicates); if (!opts.should_converge) { return PROMISE_RESULT_NOOP; } Attributes a = { {0} }; // More consideration needs to be given to using these //a.transaction = GetTransactionConstraints(pp); a.classes = GetClassDefinitionConstraints(ctx, pp); VarRef *ref = VarRefParseFromBundle(pp->promiser, PromiseGetBundle(pp)); if (strcmp("meta", pp->parent_promise_type->name) == 0) { VarRefSetMeta(ref, true); } DataType existing_value_type = CF_DATA_TYPE_NONE; const void *const existing_value = IsExpandable(pp->promiser) ? NULL : EvalContextVariableGet(ctx, ref, &existing_value_type); PromiseResult result = PROMISE_RESULT_NOOP; Rval rval = opts.cp_save->rval; if (rval.item != NULL) { DataType data_type = DataTypeFromString(opts.cp_save->lval); if (opts.cp_save->rval.type == RVAL_TYPE_FNCALL) { FnCall *fp = RvalFnCallValue(rval); const FnCallType *fn = FnCallTypeGet(fp->name); if (!fn) { assert(false && "Canary: should have been caught before this point"); FatalError(ctx, "While setting variable '%s' in bundle '%s', unknown function '%s'", pp->promiser, PromiseGetBundle(pp)->name, fp->name); } if (fn->dtype != DataTypeFromString(opts.cp_save->lval)) { FatalError(ctx, "While setting variable '%s' in bundle '%s', variable declared type '%s' but function '%s' returns type '%s'", pp->promiser, PromiseGetBundle(pp)->name, opts.cp_save->lval, fp->name, DataTypeToString(fn->dtype)); } if (existing_value_type != CF_DATA_TYPE_NONE) { // Already did this VarRefDestroy(ref); return PROMISE_RESULT_NOOP; } FnCallResult res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), fp, pp); if (res.status == FNCALL_FAILURE) { /* We do not assign variables to failed fn calls */ RvalDestroy(res.rval); VarRefDestroy(ref); return PROMISE_RESULT_NOOP; } else { rval = res.rval; } } else { Buffer *conv = BufferNew(); bool malformed = false, misprint = false; if (strcmp(opts.cp_save->lval, "int") == 0) { long int asint = IntFromString(opts.cp_save->rval.item); if (asint == CF_NOINT) { malformed = true; } else if (0 > BufferPrintf(conv, "%ld", asint)) { misprint = true; } else { rval = RvalNew(BufferData(conv), opts.cp_save->rval.type); } } else if (strcmp(opts.cp_save->lval, "real") == 0) { double real_value; if (!DoubleFromString(opts.cp_save->rval.item, &real_value)) { malformed = true; } else if (0 > BufferPrintf(conv, "%lf", real_value)) { misprint = true; } else { rval = RvalNew(BufferData(conv), opts.cp_save->rval.type); } } else { rval = RvalCopy(opts.cp_save->rval); } BufferDestroy(conv); if (malformed) { /* Arises when opts->cp_save->rval.item isn't yet expanded. */ /* Has already been logged by *FromString */ VarRefDestroy(ref); return PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } else if (misprint) { /* Even though no problems with memory allocation can * get here, there might be other problems. */ UnexpectedError("Problems writing to buffer"); VarRefDestroy(ref); return PROMISE_RESULT_NOOP; } else if (rval.type == RVAL_TYPE_LIST) { Rlist *rval_list = RvalRlistValue(rval); RlistFlatten(ctx, &rval_list); rval.item = rval_list; } } if (Epimenides(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, pp->promiser, rval, 0)) { Log(LOG_LEVEL_ERR, "Variable '%s' contains itself indirectly - an unkeepable promise", pp->promiser); exit(EXIT_FAILURE); } else { /* See if the variable needs recursively expanding again */ Rval returnval = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), ref->ns, ref->scope, rval, true, pp); RvalDestroy(rval); // freed before function exit rval = returnval; } if (existing_value_type != CF_DATA_TYPE_NONE) { if (!opts.ok_redefine) /* only on second iteration, else we ignore broken promises */ { if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON && !CompareRval(existing_value, DataTypeToRvalType(existing_value_type), rval.item, rval.type)) { switch (rval.type) { case RVAL_TYPE_SCALAR: Log(LOG_LEVEL_VERBOSE, "Redefinition of a constant scalar '%s', was '%s' now '%s'", pp->promiser, (const char *)existing_value, RvalScalarValue(rval)); PromiseRef(LOG_LEVEL_VERBOSE, pp); break; case RVAL_TYPE_LIST: { Log(LOG_LEVEL_VERBOSE, "Redefinition of a constant list '%s'", pp->promiser); Writer *w = StringWriter(); RlistWrite(w, existing_value); char *oldstr = StringWriterClose(w); Log(LOG_LEVEL_VERBOSE, "Old value '%s'", oldstr); free(oldstr); w = StringWriter(); RlistWrite(w, rval.item); char *newstr = StringWriterClose(w); Log(LOG_LEVEL_VERBOSE, " New value '%s'", newstr); free(newstr); PromiseRef(LOG_LEVEL_VERBOSE, pp); } break; case RVAL_TYPE_CONTAINER: case RVAL_TYPE_FNCALL: case RVAL_TYPE_NOPROMISEE: break; } } return result; } } if (IsCf3VarString(pp->promiser)) { // Unexpanded variables, we don't do anything with RvalDestroy(rval); VarRefDestroy(ref); return result; } if (!StringMatchFull("[a-zA-Z0-9_\200-\377.]+(\\[.+\\])*", pp->promiser)) { Log(LOG_LEVEL_ERR, "Variable identifier contains illegal characters"); PromiseRef(LOG_LEVEL_ERR, pp); RvalDestroy(rval); VarRefDestroy(ref); return result; } if (rval.type == RVAL_TYPE_LIST) { if (opts.drop_undefined) { for (Rlist *rp = RvalRlistValue(rval); rp; rp = rp->next) { if (IsNakedVar(RlistScalarValue(rp), '@')) { free(rp->val.item); rp->val.item = xstrdup(CF_NULL_VALUE); } } } for (const Rlist *rp = RvalRlistValue(rval); rp; rp = rp->next) { switch (rp->val.type) { case RVAL_TYPE_SCALAR: break; default: // Cannot assign variable because value is a list containing a non-scalar item VarRefDestroy(ref); RvalDestroy(rval); return result; } } } if (ref->num_indices > 0) { if (data_type == CF_DATA_TYPE_CONTAINER) { char *lval_str = VarRefToString(ref, true); Log(LOG_LEVEL_ERR, "Cannot assign a container to an indexed variable name '%s'. Should be assigned to '%s' instead", lval_str, ref->lval); free(lval_str); VarRefDestroy(ref); RvalDestroy(rval); return result; } else { DataType existing_type = CF_DATA_TYPE_NONE; VarRef *base_ref = VarRefCopyIndexless(ref); if (EvalContextVariableGet(ctx, ref, &existing_type) && existing_type == CF_DATA_TYPE_CONTAINER) { char *lval_str = VarRefToString(ref, true); char *base_ref_str = VarRefToString(base_ref, true); Log(LOG_LEVEL_ERR, "Cannot assign value to indexed variable name '%s', because a container is already assigned to the base name '%s'", lval_str, base_ref_str); free(lval_str); free(base_ref_str); VarRefDestroy(base_ref); VarRefDestroy(ref); RvalDestroy(rval); return result; } VarRefDestroy(base_ref); } } if (!EvalContextVariablePut(ctx, ref, rval.item, DataTypeFromString(opts.cp_save->lval), "source=promise")) { Log(LOG_LEVEL_VERBOSE, "Unable to converge %s.%s value (possibly empty or infinite regression)", ref->scope, pp->promiser); PromiseRef(LOG_LEVEL_VERBOSE, pp); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } else { Rlist *promise_meta = PromiseGetConstraintAsList(ctx, "meta", pp); if (promise_meta) { StringSet *class_meta = EvalContextVariableTags(ctx, ref); Buffer *print; for (const Rlist *rp = promise_meta; rp; rp = rp->next) { StringSetAdd(class_meta, xstrdup(RlistScalarValue(rp))); print = StringSetToBuffer(class_meta, ','); Log(LOG_LEVEL_DEBUG, "Added tag %s to class %s, tags now [%s]", RlistScalarValue(rp), pp->promiser, BufferData(print)); BufferDestroy(print); } } result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE); } } else { Log(LOG_LEVEL_ERR, "Variable %s has no promised value", pp->promiser); Log(LOG_LEVEL_ERR, "Rule from %s at/before line %llu", PromiseGetBundle(pp)->source_path, (unsigned long long)opts.cp_save->offset.line); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } /* * FIXME: Variable promise are exempt from normal evaluation logic still, so * they are not pushed to evaluation stack before being evaluated. Due to * this reason, we cannot call cfPS here to set classes, as it will error * out with ProgrammingError. * * In order to support 'classes' body for variables as well, we call * ClassAuditLog explicitly. */ ClassAuditLog(ctx, pp, a, result); VarRefDestroy(ref); RvalDestroy(rval); return result; }
bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn) { /* The CF_BUFEXT extra space is there to ensure we're not reading out of * bounds in commands that carry extra binary arguments, like MD5. */ char recvbuffer[CF_BUFSIZE + CF_BUFEXT] = { 0 }; char sendbuffer[CF_BUFSIZE] = { 0 }; char filename[CF_BUFSIZE + 1]; /* +1 for appending slash sometimes */ int received; ServerFileGetState get_args = { 0 }; /* We already encrypt because of the TLS layer, no need to encrypt more. */ const int encrypted = 0; /* Legacy stuff only for old protocol. */ assert(conn->rsa_auth == 1); assert(conn->user_data_set == 1); /* Receive up to CF_BUFSIZE - 1 bytes. */ received = ReceiveTransaction(conn->conn_info, recvbuffer, NULL); if (received == -1 || received == 0) { return false; } if (strlen(recvbuffer) == 0) { Log(LOG_LEVEL_WARNING, "Got NULL transmission, skipping!"); return true; } /* Don't process request if we're signalled to exit. */ if (IsPendingTermination()) { Log(LOG_LEVEL_VERBOSE, "Server must exit, closing connection"); return false; } /* TODO break recvbuffer here: command, param1, param2 etc. */ switch (GetCommandNew(recvbuffer)) { case PROTOCOL_COMMAND_EXEC: { /* TODO check it is always file, never directory, no end with '/' */ char args[256]; int ret = sscanf(recvbuffer, "EXEC %255[^\n]", args); if (ret != 1) /* No arguments, use default args. */ { args[0] = '\0'; } if (!AllowedUser(conn->username)) { Log(LOG_LEVEL_INFO, "EXEC denied due to not allowed user: %s", conn->username); RefuseAccess(conn, recvbuffer); return true; } char arg0[PATH_MAX]; size_t zret = CommandArg0_bound(arg0, CFRUNCOMMAND, sizeof(arg0)); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(arg0, sizeof(arg0)); if (zret == (size_t) -1) { goto protocol_error; } /* TODO EXEC should not just use paths_acl access control, but * specific "path_exec" ACL. Then different command execution could be * allowed per host, and the host could even set argv[0] in his EXEC * request, rather than only the arguments. */ if (acl_CheckPath(paths_acl, arg0, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "EXEC denied due to ACL for file: %s", arg0); RefuseAccess(conn, recvbuffer); return true; } if (!MatchClasses(ctx, conn)) { Log(LOG_LEVEL_INFO, "EXEC denied due to failed class match"); Terminate(conn->conn_info); return true; } DoExec(ctx, conn, args); Terminate(conn->conn_info); return true; } case PROTOCOL_COMMAND_VERSION: snprintf(sendbuffer, sizeof(sendbuffer), "OK: %s", Version()); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; case PROTOCOL_COMMAND_GET: { int ret = sscanf(recvbuffer, "GET %d %[^\n]", &(get_args.buf_size), filename); if (ret != 2 || get_args.buf_size <= 0 || get_args.buf_size > CF_BUFSIZE) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "GET", filename); /* TODO batch all the following in one function since it's very * similar in all of GET, OPENDIR and STAT. */ size_t zret = ShortcutsExpand(filename, sizeof(filename), SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename)); if (zret == (size_t) -1) { goto protocol_error; } PathRemoveTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "GET", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to GET: %s", filename); RefuseAccess(conn, recvbuffer); return true; } memset(sendbuffer, 0, sizeof(sendbuffer)); if (get_args.buf_size >= CF_BUFSIZE) { get_args.buf_size = 2048; } /* TODO eliminate! */ get_args.conn = conn; get_args.encrypt = false; get_args.replybuff = sendbuffer; get_args.replyfile = filename; CfGetFile(&get_args); return true; } case PROTOCOL_COMMAND_OPENDIR: { memset(filename, 0, sizeof(filename)); int ret = sscanf(recvbuffer, "OPENDIR %[^\n]", filename); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "OPENDIR", filename); /* sizeof()-1 because we need one extra byte for appending '/' afterwards. */ size_t zret = ShortcutsExpand(filename, sizeof(filename) - 1, SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename) - 1); if (zret == (size_t) -1) { goto protocol_error; } /* OPENDIR *must* be directory. */ PathAppendTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "OPENDIR", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to OPENDIR: %s", filename); RefuseAccess(conn, recvbuffer); return true; } CfOpenDirectory(conn, sendbuffer, filename); return true; } case PROTOCOL_COMMAND_SYNCH: { long time_no_see = 0; memset(filename, 0, sizeof(filename)); int ret = sscanf(recvbuffer, "SYNCH %ld STAT %[^\n]", &time_no_see, filename); if (ret != 2 || filename[0] == '\0') { goto protocol_error; } time_t tloc = time(NULL); if (tloc == -1) { /* Should never happen. */ Log(LOG_LEVEL_ERR, "Couldn't read system clock. (time: %s)", GetErrorStr()); SendTransaction(conn->conn_info, "BAD: clocks out of synch", 0, CF_DONE); return true; } time_t trem = (time_t) time_no_see; int drift = (int) (tloc - trem); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "STAT", filename); /* sizeof()-1 because we need one extra byte for appending '/' afterwards. */ size_t zret = ShortcutsExpand(filename, sizeof(filename) - 1, SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename) - 1); if (zret == (size_t) -1) { RefuseAccess(conn, recvbuffer); return true; } if (IsDirReal(filename) == 1) { PathAppendTrailingSlash(filename, strlen(filename)); } else { PathRemoveTrailingSlash(filename, strlen(filename)); } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "STAT", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to STAT: %s", filename); RefuseAccess(conn, recvbuffer); return true; } if (DENYBADCLOCKS && (drift * drift > CLOCK_DRIFT * CLOCK_DRIFT)) { snprintf(sendbuffer, sizeof(sendbuffer), "BAD: Clocks are too far unsynchronized %ld/%ld", (long) tloc, (long) trem); Log(LOG_LEVEL_INFO, "denybadclocks %s", sendbuffer); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; } else { Log(LOG_LEVEL_DEBUG, "Clocks were off by %ld", (long) tloc - (long) trem); StatFile(conn, sendbuffer, filename); } return true; } case PROTOCOL_COMMAND_MD5: { int ret = sscanf(recvbuffer, "MD5 %[^\n]", filename); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "MD5", filename); /* TODO batch all the following in one function since it's very * similar in all of GET, OPENDIR and STAT. */ size_t zret = ShortcutsExpand(filename, sizeof(filename), SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename)); if (zret == (size_t) -1) { goto protocol_error; } PathRemoveTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "MD5", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to file: %s", filename); RefuseAccess(conn, recvbuffer); return true; } assert(CF_DEFAULT_DIGEST_LEN <= EVP_MAX_MD_SIZE); unsigned char digest[EVP_MAX_MD_SIZE + 1]; assert(CF_BUFSIZE + CF_SMALL_OFFSET + CF_DEFAULT_DIGEST_LEN <= sizeof(recvbuffer)); memcpy(digest, recvbuffer + strlen(recvbuffer) + CF_SMALL_OFFSET, CF_DEFAULT_DIGEST_LEN); CompareLocalHash(filename, digest, sendbuffer); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; } case PROTOCOL_COMMAND_VAR: { char var[256]; int ret = sscanf(recvbuffer, "VAR %255[^\n]", var); if (ret != 1) { goto protocol_error; } /* TODO if this is literals_acl, then when should I check vars_acl? */ if (acl_CheckExact(literals_acl, var, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to variable: %s", var); RefuseAccess(conn, recvbuffer); return true; } GetServerLiteral(ctx, conn, sendbuffer, recvbuffer, encrypted); return true; } case PROTOCOL_COMMAND_CONTEXT: { char client_regex[256]; int ret = sscanf(recvbuffer, "CONTEXT %255[^\n]", client_regex); if (ret != 1) { goto protocol_error; } /* WARNING: this comes from legacy code and must be killed if we care * about performance. We should not accept regular expressions from * the client, but this will break backwards compatibility. * * I replicated the code in raw form here to emphasize complexity, * it's the only *slow* command currently in the protocol. */ Item *persistent_classes = ListPersistentClasses(); Item *matched_classes = NULL; /* For all persistent classes */ for (Item *ip = persistent_classes; ip != NULL; ip = ip->next) { const char *class_name = ip->name; /* Does this class match the regex the client sent? */ if (StringMatchFull(client_regex, class_name)) { /* For all ACLs */ for (size_t i = 0; i < classes_acl->len; i++) { struct resource_acl *racl = &classes_acl->acls[i]; /* Does this ACL apply to this host? */ if (access_CheckResource(racl, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == true) { const char *allowed_classes_regex = classes_acl->resource_names->list[i]->str; /* Does this ACL admits access for this class to the * connected host? */ if (StringMatchFull(allowed_classes_regex, class_name)) { PrependItem(&matched_classes, class_name, NULL); } } } } } if (matched_classes == NULL) { Log(LOG_LEVEL_INFO, "No allowed classes for remoteclassesmatching: %s", client_regex); RefuseAccess(conn, recvbuffer); return true; } ReplyServerContext(conn, encrypted, matched_classes); return true; } case PROTOCOL_COMMAND_QUERY: { char query[256], name[128]; int ret1 = sscanf(recvbuffer, "QUERY %255[^\n]", query); int ret2 = sscanf(recvbuffer, "QUERY %127s", name); if (ret1 != 1 || ret2 != 1) { goto protocol_error; } if (acl_CheckExact(query_acl, name, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to query: %s", query); RefuseAccess(conn, recvbuffer); return true; } if (GetServerQuery(conn, recvbuffer, encrypted)) { return true; } break; } case PROTOCOL_COMMAND_CALL_ME_BACK: if (acl_CheckExact(query_acl, "collect_calls", conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to Call-Collect, check the ACL for class: collect_calls"); return false; } ReceiveCollectCall(conn); /* On success that returned true; otherwise, it did all * relevant Log()ging. Either way, it closed the connection, * so we're no longer busy with it: */ return false; case PROTOCOL_COMMAND_BAD: Log(LOG_LEVEL_WARNING, "Unexpected protocol command: %s", recvbuffer); } /* We should only reach this point if something went really bad, and * close connection. In all other cases (like access denied) connection * shouldn't be closed. * TODO So we need this function to return more than true/false, because * now we return true even when access is denied! E.g. return -1 for * error, 0 on success, 1 on access denied. It can be an option if * connection will close on denial. */ protocol_error: strcpy(sendbuffer, "BAD: Request denied"); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); Log(LOG_LEVEL_INFO, "Closing connection due to request: %s", recvbuffer); return false; }
/** * Run this function on every resource (file, class, var etc) access to * grant/deny rights. Currently it checks if: * 1. #ipaddr matches the subnet expression in {admit,deny}_ips * 2. #hostname matches the subdomain expression in {admit,deny}_hostnames * 3. #key is found exactly as it is in {admit,deny}_keys * * @return Default is false, i.e. deny. If a match is found in #acl->admit.* * then return true, unless a match is also found in #acl->deny.* in * which case return false. * * @TODO preprocess our global ACL the moment a client connects, and store in * ServerConnectionState a list of objects that he can access. That way * only his relevant resources will be stored in e.g. {admit,deny}_paths * lists, and running through these two lists on every file request will * be much faster. */ bool access_CheckResource(const struct resource_acl *acl, const char *ipaddr, const char *hostname, const char *key) { /* Only hostname may be NULL in case of resolution failure. */ assert(ipaddr != NULL); assert(key != NULL); bool access = false; /* DENY by default */ /* First we check for admission, secondly for denial, so that denial takes * precedence. */ if (acl->admit.ips) { /* Still using legacy code here, doing linear search over all IPs in * textual representation... too CPU intensive! TODO store the ACL as * one list of struct sockaddr_storage, together with CIDR notation * subnet length. */ const char *rule = NULL; for (int i = 0; i < StrList_Len(acl->admit.ips); i++) { if (FuzzySetMatch(StrList_At(acl->admit.ips, i), ipaddr) == 0 || /* Legacy regex matching, TODO DEPRECATE */ StringMatchFull(StrList_At(acl->admit.ips, i), ipaddr)) { rule = StrList_At(acl->admit.ips, i); break; } } if (rule != NULL) { Log(LOG_LEVEL_DEBUG, "Admit IP due to rule: %s", rule); access = true; } } if (!access && acl->admit.hostnames != NULL && hostname != NULL && *hostname != '\0') { size_t pos = StrList_SearchLongestPrefix(acl->admit.hostnames, hostname, 0, '.', false); /* === Legacy regex matching, slow, TODO DEPRECATE === */ if (pos == (size_t) -1) { for (int i = 0; i < StrList_Len(acl->admit.hostnames); i++) { if (StringMatchFull(StrList_At(acl->admit.hostnames, i), hostname)) { pos = i; break; } } } /* =================================================== */ if (pos != (size_t) -1) { Log(LOG_LEVEL_DEBUG, "Admit hostname due to rule: %s", StrList_At(acl->admit.hostnames, pos)); access = true; } } if (!access && acl->admit.keys != NULL) { size_t pos; bool ret = StrList_BinarySearch(acl->admit.keys, key, &pos); if (ret) { Log(LOG_LEVEL_DEBUG, "Admit key due to rule: %s", StrList_At(acl->admit.keys, pos)); access = true; } } /* If access has been granted, we might need to deny it based on ACL. */ if (access && acl->deny.ips != NULL) { const char *rule = NULL; for (int i = 0; i < StrList_Len(acl->deny.ips); i++) { if (FuzzySetMatch(StrList_At(acl->deny.ips, i), ipaddr) == 0 || /* Legacy regex matching, TODO DEPRECATE */ StringMatchFull(StrList_At(acl->deny.ips, i), ipaddr)) { rule = StrList_At(acl->deny.ips, i); break; } } if (rule != NULL) { Log(LOG_LEVEL_DEBUG, "Deny IP due to rule: %s", rule); access = false; } } if (access && acl->deny.hostnames != NULL && hostname != NULL && *hostname != '\0') { size_t pos = StrList_SearchLongestPrefix(acl->deny.hostnames, hostname, 0, '.', false); /* === Legacy regex matching, slow, TODO DEPRECATE === */ if (pos == (size_t) -1) { for (int i = 0; i < StrList_Len(acl->deny.hostnames); i++) { if (StringMatchFull(StrList_At(acl->deny.hostnames, i), hostname)) { pos = i; break; } } } /* =================================================== */ if (pos != (size_t) -1) { Log(LOG_LEVEL_DEBUG, "Deny hostname due to rule: %s", StrList_At(acl->deny.hostnames, pos)); access = false; } } if (access && acl->deny.keys != NULL) { size_t pos; bool ret = StrList_BinarySearch(acl->deny.keys, key, &pos); if (ret) { Log(LOG_LEVEL_DEBUG, "Deny key due to rule: %s", StrList_At(acl->deny.keys, pos)); access = false; } } return access; }
/** * Run this function on every resource (file, class, var etc) access to * grant/deny rights. Currently it checks if: * 1. #ipaddr matches the subnet expression in {admit,deny}_ips * 2. #hostname matches the subdomain expression in {admit,deny}_hostnames * 3. #key is found exactly as it is in {admit,deny}_keys * * @return Default is false, i.e. deny. If a match is found in #acl->admit.* * then return true, unless a match is also found in #acl->deny.* in * which case return false. * * @TODO preprocess our global ACL the moment a client connects, and store in * ServerConnectionState a list of objects that he can access. That way * only his relevant resources will be stored in e.g. {admit,deny}_paths * lists, and running through these two lists on every file request will * be much faster. */ bool access_CheckResource(const struct resource_acl *acl, const char *ipaddr, const char *hostname, const char *key) { /* Only hostname may be NULL in case of resolution failure. */ assert(ipaddr != NULL); assert(key != NULL); size_t pos = (size_t) -1; bool access = false; /* DENY by default */ /* First we check for admission, secondly for denial, so that denial takes * precedence. */ const char *rule; if (acl->admit.ips) { /* Still using legacy code here, doing linear search over all IPs in * textual representation... too CPU intensive! TODO store the ACL as * one list of struct sockaddr_storage, together with CIDR notation * subnet length. */ bool found_rule = false; for (int i = 0; i < StrList_Len(acl->admit.ips); i++) { if (FuzzySetMatch(StrList_At(acl->admit.ips, i), MapAddress(ipaddr)) == 0 || /* Legacy regex matching, TODO DEPRECATE */ StringMatchFull(StrList_At(acl->admit.ips, i), MapAddress(ipaddr))) { found_rule = true; rule = StrList_At(acl->admit.ips, i); break; } } if (found_rule) { Log(LOG_LEVEL_DEBUG, "access_Check: admit IP: %s", rule); access = true; } } if (!access && acl->admit.keys != NULL) { bool ret = StrList_BinarySearch(acl->admit.keys, key, &pos); if (ret) { rule = acl->admit.keys->list[pos]->str; Log(LOG_LEVEL_DEBUG, "access_Check: admit key: %s", rule); access = true; } } if (!access && acl->admit.hostnames != NULL && hostname != NULL) { size_t hostname_len = strlen(hostname); size_t pos = StrList_SearchForPrefix(acl->admit.hostnames, hostname, hostname_len, false); /* === Legacy regex matching, slow, TODO DEPRECATE === */ bool regex_match = false; if (pos == (size_t) -1) { for (int i = 0; i < StrList_Len(acl->admit.hostnames); i++) { if (StringMatchFull(StrList_At(acl->admit.hostnames, i), hostname)) { pos = i; break; } } } /* === === */ if (pos != (size_t) -1) { rule = acl->admit.hostnames->list[pos]->str; size_t rule_len = acl->admit.hostnames->list[pos]->len; /* The rule in the access list has to be an exact match, or be a * subdomain match (i.e. the rule begins with '.') or a regex. */ if (rule_len == hostname_len || rule[0] == '.' || regex_match) { Log(LOG_LEVEL_DEBUG, "access_Check: admit hostname: %s", rule); access = true; } } } /* If access has been granted, we might need to deny it based on ACL. */ if (access && acl->deny.ips != NULL) { bool found_rule = false; for (int i = 0; i < StrList_Len(acl->deny.ips); i++) { if (FuzzySetMatch(StrList_At(acl->deny.ips, i), MapAddress(ipaddr)) == 0 || /* Legacy regex matching, TODO DEPRECATE */ StringMatchFull(StrList_At(acl->deny.ips, i), MapAddress(ipaddr))) { found_rule = true; rule = acl->deny.ips->list[i]->str; break; } } if (found_rule) { Log(LOG_LEVEL_DEBUG, "access_Check: deny IP: %s", rule); access = false; } } if (access && acl->deny.keys != NULL) { bool ret = StrList_BinarySearch(acl->deny.keys, key, &pos); if (ret) { rule = StrList_At(acl->deny.keys, pos); Log(LOG_LEVEL_DEBUG, "access_Check: deny key: %s", rule); access = false; } } if (access && acl->deny.hostnames != NULL && hostname != NULL) { size_t hostname_len = strlen(hostname); size_t pos = StrList_SearchForPrefix(acl->deny.hostnames, hostname, hostname_len, false); /* === Legacy regex matching, slow, TODO DEPRECATE === */ bool regex_match = false; if (pos == (size_t) -1) { for (int i = 0; i < StrList_Len(acl->deny.hostnames); i++) { if (StringMatchFull(StrList_At(acl->deny.hostnames, i), hostname)) { pos = i; break; } } } /* === === */ if (pos != (size_t) -1) { rule = acl->deny.hostnames->list[pos]->str; size_t rule_len = acl->deny.hostnames->list[pos]->len; /* The rule in the access list has to be an exact match, or be a * subdomain match (i.e. the rule begins with '.') or a regex. */ if (rule_len == hostname_len || rule[0] == '.' || regex_match) { Log(LOG_LEVEL_DEBUG, "access_Check: deny hostname: %s", rule); access = false; } } } return access; }
/* Checks the "varadmit" legacy ACL. */ static Item *ContextAccessControl(EvalContext *ctx, char *in, ServerConnectionState *conn, int encrypt) { Auth *ap; int access = false; char client_regex[CF_BUFSIZE]; Item *ip, *matches = NULL; int ret = sscanf(in, "CONTEXT %255[^\n]", client_regex); Item *persistent_classes = ListPersistentClasses(); if (ret != 1 || persistent_classes == NULL) { return NULL; } for (ip = persistent_classes; ip != NULL; ip = ip->next) { /* Does the class match the regex that the agent requested? */ if (StringMatchFull(client_regex, ip->name)) { for (ap = SV.varadmit; ap != NULL; ap = ap->next) { /* Does the class match any of the regex in ACLs? */ if (StringMatchFull(ap->path, ip->name)) { Log(LOG_LEVEL_VERBOSE, "Found a matching rule in access list (%s in %s)", ip->name, ap->path); if (ap->classpattern == false) { Log(LOG_LEVEL_ERR, "Context %s requires a literal server item...cannot set variable directly by path", ap->path); access = false; continue; } if ((!encrypt) && (ap->encrypt == true)) { Log(LOG_LEVEL_ERR, "Context %s requires encrypt connection...will not serve", ip->name); access = false; break; } else { Log(LOG_LEVEL_DEBUG, "Checking whether to map root privileges"); if ((IsMatchItemIn(ap->maproot, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->maproot, conn->hostname))) { conn->maproot = true; Log(LOG_LEVEL_VERBOSE, "Mapping root privileges"); } else { Log(LOG_LEVEL_VERBOSE, "No root privileges granted"); } if ((IsMatchItemIn(ap->accesslist, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->accesslist, conn->hostname))) { access = true; Log(LOG_LEVEL_DEBUG, "Access privileges - match found"); } } } } for (ap = SV.vardeny; ap != NULL; ap = ap->next) { if (strcmp(ap->path, ip->name) == 0) { if ((IsMatchItemIn(ap->accesslist, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->accesslist, conn->hostname))) { access = false; Log(LOG_LEVEL_VERBOSE, "Host %s explicitly denied access to context %s", conn->hostname, ip->name); break; } } } if (access) { Log(LOG_LEVEL_VERBOSE, "Host %s granted access to context '%s'", conn->hostname, ip->name); AppendItem(&matches, ip->name, NULL); if (encrypt && LOGENCRYPT) { /* Log files that were marked as requiring encryption */ Log(LOG_LEVEL_INFO, "Host %s granted access to context '%s'", conn->hostname, ip->name); } } else { Log(LOG_LEVEL_VERBOSE, "Host %s denied access to context '%s'", conn->hostname, ip->name); } } } DeleteItemList(persistent_classes); return matches; }
static PromiseResult MonExtractValueFromStream(EvalContext *ctx, const char *handle, Item *stream, Attributes a, const Promise *pp, double *value_out) { char value[CF_MAXVARSIZE]; int count = 1, found = false, match_count = 0, done = false; double real_val = 0; Item *ip, *match = NULL; bool ok_conversion = true; for (ip = stream; ip != NULL; ip = ip->next) { if (count == a.measure.select_line_number) { found = true; match = ip; match_count++; } if (a.measure.select_line_matching && StringMatchFull(a.measure.select_line_matching, ip->name)) { Log(LOG_LEVEL_VERBOSE, " ?? Look for %s regex %s", handle, a.measure.select_line_matching); found = true; match = ip; if (a.measure.extraction_regex) { switch (a.measure.data_type) { case CF_DATA_TYPE_INT: case CF_DATA_TYPE_REAL: strncpy(value, ExtractFirstReference(a.measure.extraction_regex, match->name), CF_MAXVARSIZE - 1); if (strcmp(value, "CF_NOMATCH") == 0) { ok_conversion = false; Log(LOG_LEVEL_VERBOSE, "Was not able to match a value with '%s' on '%s'", a.measure.extraction_regex, match->name); } else { if (ok_conversion) { Log(LOG_LEVEL_VERBOSE, "Found candidate match value of '%s'", value); if (a.measure.policy == MEASURE_POLICY_SUM || a.measure.policy == MEASURE_POLICY_AVERAGE) { double delta = 0; if (DoubleFromString(value, &delta)) { real_val += delta; } else { Log(LOG_LEVEL_ERR, "Error in double conversion from string value: %s", value); return false; } } else { if (!DoubleFromString(value, &real_val)) { Log(LOG_LEVEL_ERR, "Error in double conversion from string value: %s", value); return false; } } match_count++; if (a.measure.policy == MEASURE_POLICY_FIRST) { done = true; } } } break; default: Log(LOG_LEVEL_ERR, "Unexpected data type in data_type attribute: %d", a.measure.data_type); } } } count++; if (done) { break; } } if (!found) { cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_FAIL, pp, a, "Could not locate the line for promise '%s'", handle); *value_out = 0.0; return PROMISE_RESULT_FAIL; } switch (a.measure.data_type) { case CF_DATA_TYPE_COUNTER: real_val = (double) match_count; break; case CF_DATA_TYPE_INT: if (match_count > 1) { Log(LOG_LEVEL_INFO, "Warning: %d lines matched the line_selection \"%s\"- making best average", match_count, a.measure.select_line_matching); } if (match_count > 0 && a.measure.policy == MEASURE_POLICY_AVERAGE) // If not "average" then "sum" { real_val /= match_count; } break; case CF_DATA_TYPE_REAL: if (match_count > 1) { Log(LOG_LEVEL_INFO, "Warning: %d lines matched the line_selection \"%s\"- making best average", match_count, a.measure.select_line_matching); } if (match_count > 0) { real_val /= match_count; } break; default: Log(LOG_LEVEL_ERR, "Unexpected data type in data_type attribute: %d", a.measure.data_type); } if (!ok_conversion) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Unable to extract a value from the matched line '%s'", match->name); PromiseRef(LOG_LEVEL_INFO, pp); *value_out = 0.0; return PROMISE_RESULT_FAIL; } Log(LOG_LEVEL_INFO, "Extracted value \"%f\" for promise \"%s\"", real_val, handle); *value_out = real_val; return PROMISE_RESULT_NOOP; }