1 /*
2   Copyright 2021 Northern.tech AS
3 
4   This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 3.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 
25 #include <verify_processes.h>
26 
27 #include <actuator.h>
28 #include <processes_select.h>
29 #include <eval_context.h>
30 #include <promises.h>
31 #include <class.h>
32 #include <vars.h>
33 #include <class.h>
34 #include <item_lib.h>
35 #include <conversion.h>
36 #include <matching.h>
37 #include <attributes.h>
38 #include <locks.h>
39 #include <exec_tools.h>
40 #include <rlist.h>
41 #include <policy.h>
42 #include <scope.h>
43 #include <ornaments.h>
44 
45 static PromiseResult VerifyProcesses(EvalContext *ctx, const Attributes *a, const Promise *pp);
46 static bool ProcessSanityChecks(const Attributes *a, const Promise *pp);
47 static PromiseResult VerifyProcessOp(EvalContext *ctx, const Attributes *a, const Promise *pp);
48 static int FindPidMatches(Item **killlist, const Attributes *a, const char *promiser);
49 
VerifyProcessesPromise(EvalContext * ctx,const Promise * pp)50 PromiseResult VerifyProcessesPromise(EvalContext *ctx, const Promise *pp)
51 {
52     Attributes a = GetProcessAttributes(ctx, pp);
53     ProcessSanityChecks(&a, pp);
54 
55     return VerifyProcesses(ctx, &a, pp);
56 }
57 
58 /*****************************************************************************/
59 /* Level                                                                     */
60 /*****************************************************************************/
61 
ProcessSanityChecks(const Attributes * a,const Promise * pp)62 static bool ProcessSanityChecks(const Attributes *a, const Promise *pp)
63 {
64     assert(a != NULL);
65     assert(pp != NULL);
66     // Note that an undefined match_value results in process_count.min_range and max_range being set to CF_NOINT
67     bool promised_zero = ((a->process_count.min_range == 0) && (a->process_count.max_range == 0));
68     bool ret = true;
69 
70     if (a->restart_class)
71     {
72         if ((RlistKeyIn(a->signals, "term")) || (RlistKeyIn(a->signals, "kill")))
73         {
74             Log(LOG_LEVEL_WARNING, "Promise '%s' kills then restarts - never strictly converges",
75                 pp->promiser);
76             PromiseRef(LOG_LEVEL_INFO, pp);
77         }
78 
79         if (a->haveprocess_count)
80         {
81             Log(LOG_LEVEL_VERBOSE,
82                 "Note both process_count and restart_class define classes. Please exercises caution that there is not a logic error. Review process_count match_range, in_range_define, and out_of_range_define with respect to restart_class.");
83             PromiseRef(LOG_LEVEL_VERBOSE, pp);
84             ret = false;
85         }
86     }
87 
88     if (promised_zero && (a->restart_class))
89     {
90         Log(LOG_LEVEL_ERR, "Promise constraint conflicts - '%s' processes cannot have zero count if restarted",
91             pp->promiser);
92         PromiseRef(LOG_LEVEL_ERR, pp);
93         ret = false;
94     }
95 
96     if ((a->haveselect) && (!a->process_select.process_result))
97     {
98         Log(LOG_LEVEL_ERR, "Process select constraint body promised no result (check body definition)");
99         PromiseRef(LOG_LEVEL_ERR, pp);
100         ret = false;
101     }
102 
103     // CF_NOINT is the value assigned when match_range is not specified
104     if ((a->haveprocess_count) && ((a->process_count.min_range == CF_NOINT) || (a->process_count.max_range == CF_NOINT)))
105     {
106         Log(LOG_LEVEL_ERR, "process_count used without match_range, this promise will not define a class based on a count of matching promises.");
107         PromiseRef(LOG_LEVEL_ERR, pp);
108         ret = false;
109     }
110 
111     if ((a->haveprocess_count) && !((a->process_count.in_range_define) || (a->process_count.out_of_range_define)))
112     {
113         Log(LOG_LEVEL_ERR, "process_count body is insufficiently defined. process_count must specify at least one of in_range_define or out_of_range_define. ");
114         PromiseRef(LOG_LEVEL_ERR, pp);
115         ret = false;
116     }
117 
118 
119     return ret;
120 }
121 
122 /*****************************************************************************/
123 
VerifyProcesses(EvalContext * ctx,const Attributes * a,const Promise * pp)124 static PromiseResult VerifyProcesses(EvalContext *ctx, const Attributes *a, const Promise *pp)
125 {
126     CfLock thislock;
127     char lockname[CF_BUFSIZE];
128 
129     if (a->restart_class)
130     {
131         snprintf(lockname, CF_BUFSIZE - 1, "proc-%s-%s", pp->promiser, a->restart_class);
132     }
133     else
134     {
135         snprintf(lockname, CF_BUFSIZE - 1, "proc-%s-norestart", pp->promiser);
136     }
137 
138     thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME,
139         a->transaction.ifelapsed, a->transaction.expireafter, pp, false);
140     if (thislock.lock == NULL)
141     {
142         return PROMISE_RESULT_SKIPPED;
143     }
144 
145     PromiseBanner(ctx, pp);
146     PromiseResult result = VerifyProcessOp(ctx, a, pp);
147 
148     YieldCurrentLock(thislock);
149 
150     return result;
151 }
152 
ProcessCountMaybeDefineClass(EvalContext * const ctx,const Rlist * const rp,const Promise * const pp,const char * const attribute_name)153 static void ProcessCountMaybeDefineClass(
154     EvalContext *const ctx,
155     const Rlist *const rp,
156     const Promise *const pp,
157     const char *const attribute_name)
158 {
159     assert(ctx != NULL);
160     assert(rp != NULL);
161     assert(pp != NULL);
162     assert(attribute_name != NULL && strlen(attribute_name) > 0);
163 
164     if (rp->val.type == RVAL_TYPE_FNCALL)
165     {
166         Log(LOG_LEVEL_ERR,
167             "Body (process_count), for processes promise %s, has a failing function call (%s) in the %s attribute - cannot define the class",
168             pp->promiser,
169             RlistFnCallValue(rp)->name,
170             attribute_name);
171         return;
172     }
173 
174     assert(rp->val.type == RVAL_TYPE_SCALAR);
175     ClassRef ref = ClassRefParse(RlistScalarValue(rp));
176     EvalContextClassPutSoft(
177         ctx, RlistScalarValue(rp), CONTEXT_SCOPE_NAMESPACE, "source=promise");
178     ClassRefDestroy(ref);
179 }
180 
VerifyProcessOp(EvalContext * ctx,const Attributes * a,const Promise * pp)181 static PromiseResult VerifyProcessOp(EvalContext *ctx, const Attributes *a, const Promise *pp)
182 {
183     assert(a != NULL);
184     assert(pp != NULL);
185     bool do_signals = true;
186     int out_of_range;
187     int killed = 0;
188     bool need_to_restart = true;
189     Item *killlist = NULL;
190 
191     int matches = FindPidMatches(&killlist, a, pp->promiser);
192 
193 /* promise based on number of matches */
194 
195     PromiseResult result = PROMISE_RESULT_NOOP;
196     if (a->process_count.min_range != CF_NOINT)  /* if a range is specified */
197     {
198         if ((matches < a->process_count.min_range) || (matches > a->process_count.max_range))
199         {
200             cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Process count for '%s' was out of promised range (%d found)", pp->promiser, matches);
201             result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
202             for (const Rlist *rp = a->process_count.out_of_range_define; rp != NULL; rp = rp->next)
203             {
204                 ProcessCountMaybeDefineClass(ctx, rp, pp, "out_of_range_define");
205             }
206             out_of_range = true;
207         }
208         else
209         {
210             for (const Rlist *rp = a->process_count.in_range_define; rp != NULL; rp = rp->next)
211             {
212                 ProcessCountMaybeDefineClass(ctx, rp, pp, "in_range_define");
213             }
214             cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Process promise for '%s' is kept", pp->promiser);
215             out_of_range = false;
216         }
217     }
218     else
219     {
220         out_of_range = true;
221     }
222 
223     if (!out_of_range)
224     {
225         DeleteItemList(killlist);
226         return result;
227     }
228 
229     if (a->transaction.action == cfa_warn)
230     {
231         do_signals = false;
232         result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
233     }
234     else
235     {
236         do_signals = true;
237     }
238 
239 /* signal/kill promises for existing matches */
240 
241     if (do_signals && (matches > 0))
242     {
243         if (a->process_stop != NULL)
244         {
245             if (DONTDO)
246             {
247                 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, a,
248                      "Need to keep process-stop promise for '%s', but only a warning is promised", pp->promiser);
249                 result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
250             }
251             else
252             {
253                 if (IsExecutable(CommandArg0(a->process_stop)))
254                 {
255                     Log(LOG_LEVEL_DEBUG, "Found process_stop command '%s' is executable.", a->process_stop);
256 
257                     if (ShellCommandReturnsZero(a->process_stop, SHELL_TYPE_NONE))
258                     {
259                         cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, a,
260                              "Promise to stop '%s' repaired, '%s' returned zero",
261                              pp->promiser, a->process_stop);
262                         result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
263                     }
264                     else
265                     {
266                         cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
267                              "Promise to stop '%s' failed, '%s' returned nonzero",
268                              pp->promiser, a->process_stop);
269                         result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
270                     }
271                 }
272                 else
273                 {
274                     cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
275                          "Process promise to stop '%s' could not be kept because '%s' is not executable",
276                          pp->promiser, a->process_stop);
277                     result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
278                     DeleteItemList(killlist);
279                     return result;
280                 }
281             }
282         }
283 
284         killed = DoAllSignals(ctx, killlist, a, pp, &result);
285     }
286 
287 /* delegated promise to restart killed or non-existent entries */
288 
289     need_to_restart = (a->restart_class != NULL) && (killed || (matches == 0));
290 
291     DeleteItemList(killlist);
292 
293     if (!need_to_restart)
294     {
295         cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "No restart promised for %s", pp->promiser);
296         return result;
297     }
298     else
299     {
300         if (a->transaction.action == cfa_warn)
301         {
302             cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a,
303                  "Need to keep restart promise for '%s', but only a warning is promised", pp->promiser);
304             result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
305         }
306         else
307         {
308             PromiseResult status = killed ? PROMISE_RESULT_CHANGE : PROMISE_RESULT_NOOP;
309             cfPS(ctx, LOG_LEVEL_VERBOSE, status, pp, a,
310                  "C:     +  Global class: %s ", a->restart_class);
311             result = PromiseResultUpdate(result, status);
312             EvalContextClassPutSoft(ctx, a->restart_class, CONTEXT_SCOPE_NAMESPACE, "source=promise");
313         }
314     }
315 
316     return result;
317 }
318 
319 #ifndef __MINGW32__
DoAllSignals(EvalContext * ctx,Item * siglist,const Attributes * a,const Promise * pp,PromiseResult * result)320 int DoAllSignals(EvalContext *ctx, Item *siglist, const Attributes *a, const Promise *pp, PromiseResult *result)
321 {
322     Item *ip;
323     Rlist *rp;
324     pid_t pid;
325     int killed = false;
326     bool failure = false;
327 
328     if (siglist == NULL)
329     {
330         return 0;
331     }
332 
333     if (a->signals == NULL)
334     {
335         Log(LOG_LEVEL_VERBOSE, "No signals to send for '%s'", pp->promiser);
336         return 0;
337     }
338 
339     for (ip = siglist; ip != NULL; ip = ip->next)
340     {
341         pid = ip->counter;
342 
343         for (rp = a->signals; rp != NULL; rp = rp->next)
344         {
345             int signal = SignalFromString(RlistScalarValue(rp));
346 
347             if (!DONTDO)
348             {
349                 if ((signal == SIGKILL) || (signal == SIGTERM))
350                 {
351                     killed = true;
352                 }
353 
354                 if (kill(pid, signal) < 0)
355                 {
356                     cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_FAIL, pp, a,
357                          "Couldn't send promised signal '%s' (%d) to pid %jd (might be dead). (kill: %s)",
358                          RlistScalarValue(rp), signal, (intmax_t)pid, GetErrorStr());
359                     failure = true;
360                 }
361                 else
362                 {
363                     cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, a,
364                          "Signalled '%s' (%d) to process %jd (%s)",
365                          RlistScalarValue(rp), signal, (intmax_t) pid, ip->name);
366                     *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
367                     failure = false;
368                 }
369             }
370             else
371             {
372                 Log(LOG_LEVEL_ERR, "Need to keep signal promise '%s' in process entry '%s'",
373                     RlistScalarValue(rp), ip->name);
374             }
375         }
376     }
377 
378     if (failure)
379     {
380         *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
381     }
382 
383     return killed;
384 }
385 #endif
386 
FindPidMatches(Item ** killlist,const Attributes * a,const char * promiser)387 static int FindPidMatches(Item **killlist, const Attributes *a, const char *promiser)
388 {
389     int matches = 0;
390     pid_t cfengine_pid = getpid();
391 
392     Item *matched = SelectProcesses(promiser, &(a->process_select), a->haveselect);
393 
394     for (Item *ip = matched; ip != NULL; ip = ip->next)
395     {
396         pid_t pid = ip->counter;
397 
398         if (a->signals) /* There are some processes we don't want to signal. */
399         {
400             if (pid == 1)
401             {
402                 if (RlistLen(a->signals) == 1 && RlistKeyIn(a->signals, "hup"))
403                 {
404                     Log(LOG_LEVEL_VERBOSE, "Okay to send only HUP to init");
405                 }
406                 else
407                 {
408                     continue;
409                 }
410             }
411             else if (pid < 4)
412             {
413                 Log(LOG_LEVEL_VERBOSE, "Will not signal or restart processes 0,1,2,3 (occurred while looking for %s)",
414                     promiser);
415                 continue;
416             }
417 
418             if (pid == cfengine_pid)
419             {
420                 Log(LOG_LEVEL_VERBOSE, "cf-agent will not signal itself!");
421                 continue;
422             }
423         }
424 
425         bool promised_zero = (a->process_count.min_range == 0) && (a->process_count.max_range == 0);
426 
427         if ((a->transaction.action == cfa_warn) && promised_zero)
428         {
429             Log(LOG_LEVEL_WARNING, "Process alert '%s'", GetProcessTableLegend());
430             Log(LOG_LEVEL_WARNING, "Process alert '%s'", ip->name);
431             continue;
432         }
433 
434         if (a->transaction.action == cfa_warn)
435         {
436             Log(LOG_LEVEL_WARNING, "Matched '%s'", ip->name);
437         }
438         else
439         {
440             Log(LOG_LEVEL_VERBOSE, "Matched '%s'", ip->name);
441         }
442 
443         PrependItem(killlist, ip->name, "");
444         (*killlist)->counter = pid;
445         matches++;
446     }
447 
448     DeleteItemList(matched);
449 
450     return matches;
451 }
452