static ActionResult RepairExec(EvalContext *ctx, Attributes a, Promise *pp) { char line[CF_BUFSIZE], eventname[CF_BUFSIZE]; char cmdline[CF_BUFSIZE]; char comm[20]; int outsourced, count = 0; #if !defined(__MINGW32__) mode_t maskval = 0; #endif FILE *pfp; char cmdOutBuf[CF_BUFSIZE]; int cmdOutBufPos = 0; int lineOutLen; if (IsAbsoluteFileName(CommandArg0(pp->promiser)) || a.contain.shelltype == SHELL_TYPE_NONE) { if (!IsExecutable(CommandArg0(pp->promiser))) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "'%s' promises to be executable but isn't", pp->promiser); if (strchr(pp->promiser, ' ')) { Log(LOG_LEVEL_VERBOSE, "Paths with spaces must be inside escaped quoutes (e.g. \\\"%s\\\")", pp->promiser); } return ACTION_RESULT_FAILED; } else { Log(LOG_LEVEL_VERBOSE, "Promiser string contains a valid executable '%s' - ok", CommandArg0(pp->promiser)); } } char timeout_str[CF_BUFSIZE]; if (a.contain.timeout == CF_NOINT) { snprintf(timeout_str, CF_BUFSIZE, "no timeout"); } else { snprintf(timeout_str, CF_BUFSIZE, "timeout=%ds", a.contain.timeout); } char owner_str[CF_BUFSIZE] = ""; if (a.contain.owner != -1) { snprintf(owner_str, CF_BUFSIZE, ",uid=%ju", (uintmax_t)a.contain.owner); } char group_str[CF_BUFSIZE] = ""; if (a.contain.group != -1) { snprintf(group_str, CF_BUFSIZE, ",gid=%ju", (uintmax_t)a.contain.group); } snprintf(cmdline, CF_BUFSIZE, "%s%s%s", pp->promiser, a.args ? " " : "", a.args ? a.args : ""); Log(LOG_LEVEL_INFO, "Executing '%s%s%s' ... '%s'", timeout_str, owner_str, group_str, cmdline); BeginMeasure(); if (DONTDO && (!a.contain.preview)) { Log(LOG_LEVEL_ERR, "Would execute script '%s'", cmdline); return ACTION_RESULT_OK; } if (a.transaction.action != cfa_fix) { Log(LOG_LEVEL_ERR, "Command '%s' needs to be executed, but only warning was promised", cmdline); return ACTION_RESULT_OK; } CommandPrefix(cmdline, comm); if (a.transaction.background) { #ifdef __MINGW32__ outsourced = true; #else Log(LOG_LEVEL_VERBOSE, "Backgrounding job '%s'", cmdline); outsourced = fork(); #endif } else { outsourced = false; } if (outsourced || (!a.transaction.background)) // work done here: either by child or non-background parent { if (a.contain.timeout != CF_NOINT) { SetTimeOut(a.contain.timeout); } #ifndef __MINGW32__ Log(LOG_LEVEL_VERBOSE, "(Setting umask to %jo)", (uintmax_t)a.contain.umask); maskval = umask(a.contain.umask); if (a.contain.umask == 0) { Log(LOG_LEVEL_VERBOSE, "Programming '%s' running with umask 0! Use umask= to set", cmdline); } #endif /* !__MINGW32__ */ if (a.contain.shelltype == SHELL_TYPE_POWERSHELL) { #ifdef __MINGW32__ pfp = cf_popen_powershell_setuid(cmdline, "r", a.contain.owner, a.contain.group, a.contain.chdir, a.contain.chroot, a.transaction.background); #else // !__MINGW32__ Log(LOG_LEVEL_ERR, "Powershell is only supported on Windows"); return ACTION_RESULT_FAILED; #endif // !__MINGW32__ } else if (a.contain.shelltype == SHELL_TYPE_USE) { pfp = cf_popen_shsetuid(cmdline, "r", a.contain.owner, a.contain.group, a.contain.chdir, a.contain.chroot, a.transaction.background); } else { pfp = cf_popensetuid(cmdline, "r", a.contain.owner, a.contain.group, a.contain.chdir, a.contain.chroot, a.transaction.background); } if (pfp == NULL) { Log(LOG_LEVEL_ERR, "Couldn't open pipe to command '%s'. (cf_popen: %s)", cmdline, GetErrorStr()); return ACTION_RESULT_FAILED; } for (;;) { ssize_t res = CfReadLine(line, CF_BUFSIZE, pfp); if (res == 0) { break; } if (res == -1) { Log(LOG_LEVEL_ERR, "Unable to read output from command '%s'. (fread: %s)", cmdline, GetErrorStr()); cf_pclose(pfp); return ACTION_RESULT_FAILED; } if (strstr(line, "cfengine-die")) { break; } if (a.contain.preview) { PreviewProtocolLine(line, cmdline); } if (a.module) { ModuleProtocol(ctx, cmdline, line, !a.contain.nooutput, PromiseGetNamespace(pp)); } else if ((!a.contain.nooutput) && (!EmptyString(line))) { lineOutLen = strlen(comm) + strlen(line) + 12; // if buffer is to small for this line, output it directly if (lineOutLen > sizeof(cmdOutBuf)) { Log(LOG_LEVEL_NOTICE, "Q: '%s': %s", comm, line); } else { if (cmdOutBufPos + lineOutLen > sizeof(cmdOutBuf)) { Log(LOG_LEVEL_NOTICE, "%s", cmdOutBuf); cmdOutBufPos = 0; } sprintf(cmdOutBuf + cmdOutBufPos, "Q: \"...%s\": %s\n", comm, line); cmdOutBufPos += (lineOutLen - 1); } count++; } } #ifdef __MINGW32__ if (outsourced) // only get return value if we waited for command execution { cf_pclose(pfp); } else #endif /* __MINGW32__ */ { int ret = cf_pclose(pfp); if (ret == -1) { cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_FAIL, pp, a, "Finished script '%s' - failed (abnormal termination)", pp->promiser); } else { VerifyCommandRetcode(ctx, ret, true, a, pp); } } } if (count) { if (cmdOutBufPos) { Log(LOG_LEVEL_NOTICE, "%s", cmdOutBuf); } Log(LOG_LEVEL_INFO, "Last %d quoted lines were generated by promiser '%s'", count, cmdline); } if (a.contain.timeout != CF_NOINT) { alarm(0); signal(SIGALRM, SIG_DFL); } Log(LOG_LEVEL_INFO, "Completed execution of '%s'", cmdline); #ifndef __MINGW32__ umask(maskval); #endif snprintf(eventname, CF_BUFSIZE - 1, "Exec(%s)", cmdline); #ifndef __MINGW32__ if ((a.transaction.background) && outsourced) { Log(LOG_LEVEL_VERBOSE, "Backgrounded command '%s' is done - exiting", cmdline); exit(0); } #endif /* !__MINGW32__ */ return ACTION_RESULT_OK; }
static void VerifyExec(Attributes a, Promise *pp) { CfLock thislock; char unsafeLine[CF_BUFSIZE], line[sizeof(unsafeLine) * 2], eventname[CF_BUFSIZE]; char comm[20]; char execstr[CF_EXPANDSIZE]; int outsourced, count = 0; mode_t maskval = 0; FILE *pfp; char cmdOutBuf[CF_BUFSIZE]; int cmdOutBufPos = 0; int lineOutLen; if (!IsExecutable(GetArg0(pp->promiser))) { cfPS(cf_error, CF_FAIL, "", pp, a, "%s promises to be executable but isn't\n", pp->promiser); if (strchr(pp->promiser, ' ')) { CfOut(cf_verbose, "", "Paths with spaces must be inside escaped quoutes (e.g. \\\"%s\\\")", pp->promiser); } return; } else { CfOut(cf_verbose, "", " -> Promiser string contains a valid executable (%s) - ok\n", GetArg0(pp->promiser)); } DeleteScalar("this", "promiser"); NewScalar("this", "promiser", pp->promiser, cf_str); if (a.args) { snprintf(execstr, CF_EXPANDSIZE - 1, "%s %s", pp->promiser, a.args); } else { strncpy(execstr, pp->promiser, CF_BUFSIZE); } thislock = AcquireLock(execstr, VUQNAME, CFSTARTTIME, a, pp, false); if (thislock.lock == NULL) { return; } PromiseBanner(pp); CfOut(cf_inform, "", " -> Executing \'%s\' ...(timeout=%d,owner=%ju,group=%ju)\n", execstr, a.contain.timeout, (uintmax_t)a.contain.owner, (uintmax_t)a.contain.group); BeginMeasure(); if (DONTDO && !a.contain.preview) { CfOut(cf_error, "", "-> Would execute script %s\n", execstr); } else if (a.transaction.action != cfa_fix) { cfPS(cf_error, CF_WARN, "", pp, a, " !! Command \"%s\" needs to be executed, but only warning was promised", execstr); } else { CommPrefix(execstr, comm); if (a.transaction.background) { #ifdef MINGW outsourced = true; #else CfOut(cf_verbose, "", " -> Backgrounding job %s\n", execstr); outsourced = fork(); #endif } else { outsourced = false; } if (outsourced || !a.transaction.background) // work done here: either by child or non-background parent { if (a.contain.timeout != CF_NOINT) { SetTimeOut(a.contain.timeout); } #ifndef MINGW CfOut(cf_verbose, "", " -> (Setting umask to %jo)\n", (uintmax_t)a.contain.umask); maskval = umask(a.contain.umask); if (a.contain.umask == 0) { CfOut(cf_verbose, "", " !! Programming %s running with umask 0! Use umask= to set\n", execstr); } #endif /* NOT MINGW */ if (a.contain.useshell) { pfp = cf_popen_shsetuid(execstr, "r", a.contain.owner, a.contain.group, a.contain.chdir, a.contain.chroot, a.transaction.background); } else { pfp = cf_popensetuid(execstr, "r", a.contain.owner, a.contain.group, a.contain.chdir, a.contain.chroot, a.transaction.background); } if (pfp == NULL) { cfPS(cf_error, CF_FAIL, "cf_popen", pp, a, "!! Couldn't open pipe to command %s\n", execstr); YieldCurrentLock(thislock); return; } while (!feof(pfp)) { if (ferror(pfp)) /* abortable */ { cfPS(cf_error, CF_TIMEX, "ferror", pp, a, "!! Command pipe %s\n", execstr); cf_pclose(pfp); YieldCurrentLock(thislock); return; } CfReadLine(unsafeLine, CF_BUFSIZE - 1, pfp); ReplaceStr(unsafeLine, line, sizeof(line), "%", "%%"); // escape format char if (strstr(line, "cfengine-die")) { break; } if (ferror(pfp)) /* abortable */ { cfPS(cf_error, CF_TIMEX, "ferror", pp, a, "!! Command pipe %s\n", execstr); cf_pclose(pfp); YieldCurrentLock(thislock); return; } if (a.contain.preview) { PreviewProtocolLine(line, execstr); } if (a.module) { ModuleProtocol(execstr, line, !a.contain.nooutput); } else if (!a.contain.nooutput && NonEmptyLine(line)) { lineOutLen = strlen(comm) + strlen(line) + 12; // if buffer is to small for this line, output it directly if (lineOutLen > sizeof(cmdOutBuf)) { CfOut(cf_cmdout, "", "Q: \"...%s\": %s\n", comm, line); } else { if (cmdOutBufPos + lineOutLen > sizeof(cmdOutBuf)) { CfOut(cf_cmdout, "", "%s", cmdOutBuf); cmdOutBufPos = 0; } sprintf(cmdOutBuf + cmdOutBufPos, "Q: \"...%s\": %s\n", comm, line); cmdOutBufPos += (lineOutLen - 1); } count++; } } #ifdef MINGW if (outsourced) // only get return value if we waited for command execution { cf_pclose(pfp); } else { cf_pclose_def(pfp, a, pp); } #else /* NOT MINGW */ cf_pclose_def(pfp, a, pp); #endif } if (count) { if (cmdOutBufPos) { CfOut(cf_cmdout, "", "%s", cmdOutBuf); } CfOut(cf_cmdout, "", "I: Last %d quoted lines were generated by promiser \"%s\"\n", count, execstr); } if (a.contain.timeout != CF_NOINT) { alarm(0); signal(SIGALRM, SIG_DFL); } CfOut(cf_inform, "", " -> Completed execution of %s\n", execstr); #ifndef MINGW umask(maskval); #endif YieldCurrentLock(thislock); snprintf(eventname, CF_BUFSIZE - 1, "Exec(%s)", execstr); #ifndef MINGW if (a.transaction.background && outsourced) { CfOut(cf_verbose, "", " -> Backgrounded command (%s) is done - exiting\n", execstr); exit(0); } #endif /* NOT MINGW */ } }
static Item *MonReSample(EvalContext *ctx, int slot, Attributes a, const Promise *pp, PromiseResult *result) { CfLock thislock; char eventname[CF_BUFSIZE]; char comm[20]; struct timespec start; FILE *fin = NULL; mode_t maskval = 0; if (a.measure.stream_type && strcmp(a.measure.stream_type, "pipe") == 0) { if (!IsExecutable(CommandArg0(pp->promiser))) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "%s promises to be executable but isn't\n", pp->promiser); *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); return NULL; } else { Log(LOG_LEVEL_VERBOSE, "Promiser string contains a valid executable (%s) - ok", CommandArg0(pp->promiser)); } } TransactionContext tc = { .expireafter = a.transaction.expireafter, .ifelapsed = MONITOR_RESTARTED ? 0 : a.transaction.ifelapsed, // Force a measurement if restarted }; CFSTARTTIME = time(NULL); thislock = AcquireLock(ctx, pp->promiser, VUQNAME, CFSTARTTIME, tc, pp, false); if (thislock.lock == NULL) { if (a.measure.history_type && (strcmp(a.measure.history_type, "log") == 0)) { DeleteItemList(ENTERPRISE_DATA[slot].output); ENTERPRISE_DATA[slot].output = NULL; } else { /* If static or time-series, and too soon or busy then use a cached value to avoid artificial gaps in the history */ } MONITOR_RESTARTED = false; return ENTERPRISE_DATA[slot].output; } else { DeleteItemList(ENTERPRISE_DATA[slot].output); ENTERPRISE_DATA[slot].output = NULL; Log(LOG_LEVEL_INFO, "Sampling \'%s\' ...(timeout=%d,owner=%ju,group=%ju)", pp->promiser, a.contain.timeout, (uintmax_t)a.contain.owner, (uintmax_t)a.contain.group); start = BeginMeasure(); CommandPrefix(pp->promiser, comm); if (a.contain.timeout != 0) { SetTimeOut(a.contain.timeout); } /* Stream types */ if (a.measure.stream_type && strcmp(a.measure.stream_type, "file") == 0) { long filepos = 0; struct stat sb; Log(LOG_LEVEL_VERBOSE, "Stream \"%s\" is a plain file", pp->promiser); if (stat(pp->promiser, &sb) == -1) { Log(LOG_LEVEL_INFO, "Unable to find stream '%s'. (stat: %s)", pp->promiser, GetErrorStr()); YieldCurrentLock(thislock); MONITOR_RESTARTED = false; return NULL; } fin = safe_fopen(pp->promiser, "r"); if (a.measure.growing) { filepos = Mon_RestoreFilePosition(pp->promiser); if (sb.st_size >= filepos) { fseek(fin, filepos, SEEK_SET); } } } else if (a.measure.stream_type && strcmp(a.measure.stream_type, "pipe") == 0) { Log(LOG_LEVEL_VERBOSE, "(Setting pipe umask to %jo)", (uintmax_t)a.contain.umask); maskval = umask(a.contain.umask); if (a.contain.umask == 0) { Log(LOG_LEVEL_VERBOSE, "Programming %s running with umask 0! Use umask= to set", pp->promiser); } // Mark: This is strange that we used these wrappers. Currently no way of setting these a.contain.owner = -1; a.contain.group = -1; a.contain.chdir = NULL; a.contain.chroot = NULL; // Mark: they were unset, and would fail for non-root(!) if (a.contain.shelltype == SHELL_TYPE_POWERSHELL) { #ifdef __MINGW32__ fin = cf_popen_powershell_setuid(pp->promiser, "r", a.contain.owner, a.contain.group, a.contain.chdir, a.contain.chroot, false); #else // !__MINGW32__ Log(LOG_LEVEL_ERR, "Powershell is only supported on Windows"); YieldCurrentLock(thislock); MONITOR_RESTARTED = false; return NULL; #endif // !__MINGW32__ } else if (a.contain.shelltype == SHELL_TYPE_USE) { fin = cf_popen_shsetuid(pp->promiser, "r", a.contain.owner, a.contain.group, a.contain.chdir, a.contain.chroot, false); } else { fin = cf_popensetuid(pp->promiser, "r", a.contain.owner, a.contain.group, a.contain.chdir, a.contain.chroot, false); } } /* generic file stream */ if (fin == NULL) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Couldn't open pipe to command '%s'. (cf_popen: %s)", pp->promiser, GetErrorStr()); *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); YieldCurrentLock(thislock); MONITOR_RESTARTED = false; return ENTERPRISE_DATA[slot].output; } size_t line_size = CF_BUFSIZE; char *line = xmalloc(line_size); for (;;) { ssize_t res = CfReadLine(&line, &line_size, fin); if (res == -1) { if (!feof(fin)) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_TIMEOUT, pp, a, "Sample stream '%s'. (fread: %s)", pp->promiser, GetErrorStr()); *result = PromiseResultUpdate(*result, PROMISE_RESULT_TIMEOUT); YieldCurrentLock(thislock); free(line); return ENTERPRISE_DATA[slot].output; } else { break; } } AppendItem(&(ENTERPRISE_DATA[slot].output), line, NULL); } free(line); if (a.measure.stream_type && strcmp(a.measure.stream_type, "file") == 0) { long fileptr = ftell(fin); fclose(fin); Mon_SaveFilePosition(pp->promiser, fileptr); } else if (a.measure.stream_type && strcmp(a.measure.stream_type, "pipe") == 0) { cf_pclose(fin); } } if (a.contain.timeout != 0) { alarm(0); signal(SIGALRM, SIG_DFL); } Log(LOG_LEVEL_INFO, "Collected sample of %s", pp->promiser); umask(maskval); YieldCurrentLock(thislock); MONITOR_RESTARTED = false; snprintf(eventname, CF_BUFSIZE - 1, "Sample(%s)", pp->promiser); EndMeasure(eventname, start); return ENTERPRISE_DATA[slot].output; } /************************************************************************************/ void HistoryUpdate(EvalContext *ctx, Averages newvals) { CfLock thislock; time_t now = time(NULL); /* We do this only once per hour - this should not be changed */ Log(LOG_LEVEL_VERBOSE, "(Updating long-term history database)"); Policy *history_db_policy = PolicyNew(); Promise *pp = NULL; Bundle *bp = PolicyAppendBundle(history_db_policy, NamespaceDefault(), "history_db_bundle", "agent", NULL, NULL); PromiseType *tp = BundleAppendPromiseType(bp, "history_db"); pp = PromiseTypeAppendPromise(tp, "the long term memory", (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, NULL); assert(pp); TransactionContext tc = { .expireafter = 0, .ifelapsed = 59 }; thislock = AcquireLock(ctx, pp->promiser, VUQNAME, now, tc, pp, false); if (thislock.lock == NULL) { PolicyDestroy(history_db_policy); return; } /* Refresh the class context of the agent */ EvalContextClear(ctx); DetectEnvironment(ctx); time_t t = SetReferenceTime(); UpdateTimeClasses(ctx, t); EvalContextHeapPersistentLoadAll(ctx); LoadSystemConstants(ctx); YieldCurrentLock(thislock); PolicyDestroy(history_db_policy); Mon_HistoryUpdate(CFSTARTTIME, &newvals); Mon_DumpSlowlyVaryingObservations(); } /************************************************************************************/ static Item *MonGetMeasurementStream(EvalContext *ctx, Attributes a, const Promise *pp, PromiseResult *result) { int i; for (i = 0; i < CF_DUNBAR_WORK; i++) { if (ENTERPRISE_DATA[i].path == NULL) { break; } if (strcmp(ENTERPRISE_DATA[i].path, pp->promiser) == 0) { ENTERPRISE_DATA[i].output = MonReSample(ctx, i, a, pp, result); return ENTERPRISE_DATA[i].output; } } ENTERPRISE_DATA[i].path = xstrdup(pp->promiser); ENTERPRISE_DATA[i].output = MonReSample(ctx, i, a, pp, result); return ENTERPRISE_DATA[i].output; }