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_methods.h>
26 
27 #include <actuator.h>
28 #include <eval_context.h>
29 #include <vars.h>
30 #include <expand.h>
31 #include <files_names.h>
32 #include <scope.h>
33 #include <unix.h>
34 #include <attributes.h>
35 #include <locks.h>
36 #include <generic_agent.h> // HashVariables
37 #include <fncall.h>
38 #include <rlist.h>
39 #include <ornaments.h>
40 #include <string_lib.h>
41 
42 static void GetReturnValue(EvalContext *ctx, const Bundle *callee, const Promise *caller);
43 
44 /*****************************************************************************/
45 
46 /*
47  * This function should only be called from the evaluator so that methods promises
48  * never report REPAIRED compliance (the promises inside will do that already).
49  *
50  * Promise types that delegate to bundles (services and users) should call VerifyMethod,
51  * which maintains the REPAIRED compliance so that it bubbles up correctly to the parent
52  * promise type.
53  */
54 
VerifyMethodsPromise(EvalContext * ctx,const Promise * pp)55 PromiseResult VerifyMethodsPromise(EvalContext *ctx, const Promise *pp)
56 {
57     Attributes a = GetMethodAttributes(ctx, pp);
58 
59     const Constraint *cp;
60     Rval method_name;
61     bool destroy_name;
62 
63     if ((cp = PromiseGetConstraint(pp, "usebundle")))
64     {
65         method_name = cp->rval;
66         destroy_name = false;
67     }
68     else
69     {
70         method_name = RvalNew(pp->promiser, RVAL_TYPE_SCALAR);
71         destroy_name = true;
72     }
73 
74     PromiseResult result = VerifyMethod(ctx, method_name, &a, pp);
75 
76     if (destroy_name)
77     {
78         RvalDestroy(method_name);
79     }
80 
81     return result;
82 }
83 
84 /*****************************************************************************/
85 
VerifyMethod(EvalContext * ctx,const Rval call,const Attributes * a,const Promise * pp)86 PromiseResult VerifyMethod(EvalContext *ctx, const Rval call, const Attributes *a, const Promise *pp)
87 {
88     const Rlist *args = NULL;
89     Buffer *method_name = BufferNew();
90     switch (call.type)
91     {
92     case RVAL_TYPE_FNCALL:
93     {
94         const FnCall *fp = RvalFnCallValue(call);
95         ExpandScalar(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, fp->name, method_name);
96         args = fp->args;
97         int arg_index = 0;
98         while (args)
99         {
100            ++arg_index;
101            args = args->next;
102         }
103         args = fp->args;
104     }
105     break;
106 
107     case RVAL_TYPE_SCALAR:
108     {
109         ExpandScalar(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name,
110                      RvalScalarValue(call), method_name);
111         args = NULL;
112     }
113     break;
114 
115     default:
116         BufferDestroy(method_name);
117         return PROMISE_RESULT_NOOP;
118     }
119 
120     char lockname[CF_BUFSIZE];
121     GetLockName(lockname, "method", pp->promiser, args);
122 
123     CfLock thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME, a->transaction.ifelapsed, a->transaction.expireafter, pp, false);
124     if (thislock.lock == NULL)
125     {
126         BufferDestroy(method_name);
127         return PROMISE_RESULT_SKIPPED;
128     }
129 
130     PromiseBanner(ctx, pp);
131 
132     const Bundle *bp = EvalContextResolveBundleExpression(ctx, PromiseGetPolicy(pp), BufferData(method_name), "agent");
133 
134     if (!bp)
135     {
136         bp = EvalContextResolveBundleExpression(ctx, PromiseGetPolicy(pp), BufferData(method_name), "common");
137     }
138 
139     PromiseResult result = PROMISE_RESULT_NOOP;
140 
141     if (bp)
142     {
143         if (a->transaction.action == cfa_warn) // don't skip for dry-runs (ie ignore DONTDO)
144         {
145             result = PROMISE_RESULT_WARN;
146             cfPS(ctx, LOG_LEVEL_WARNING, result, pp, a, "Bundle '%s' should be invoked, but only a warning was promised!", BufferData(method_name));
147         }
148         else
149         {
150             BundleBanner(bp, args);
151             EvalContextSetBundleArgs(ctx, args);
152             EvalContextStackPushBundleFrame(ctx, bp, args, a->inherit);
153 
154             /* Clear all array-variables that are already set in the sub-bundle.
155                Otherwise, array-data accumulates between multiple bundle evaluations.
156                Note: for bundles invoked multiple times via bundlesequence, array
157                data *does* accumulate. */
158             VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, bp->ns, bp->name, NULL);
159             Variable *var;
160             while ((var = VariableTableIteratorNext(iter)))
161             {
162                 const VarRef *var_ref = VariableGetRef(var);
163                 if (!var_ref->num_indices)
164                 {
165                     continue;
166                 }
167                 EvalContextVariableRemove(ctx, var_ref);
168             }
169             VariableTableIteratorDestroy(iter);
170 
171             BundleResolve(ctx, bp);
172 
173             result = ScheduleAgentOperations(ctx, bp);
174 
175             GetReturnValue(ctx, bp, pp);
176 
177             EvalContextStackPopFrame(ctx);
178             EvalContextSetBundleArgs(ctx, NULL);
179             switch (result)
180             {
181             case PROMISE_RESULT_SKIPPED:
182                 // if a bundle returns 'skipped', meaning that all promises were locked in the bundle,
183                 // we explicitly consider the method 'kept'
184                 result = PROMISE_RESULT_NOOP;
185                 // intentional fallthru
186 
187             case PROMISE_RESULT_NOOP:
188                 cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Method '%s' verified", bp->name);
189                 break;
190 
191             case PROMISE_RESULT_WARN:
192                 cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, "Method '%s' invoked repairs, but only warnings promised", bp->name);
193                 break;
194 
195             case PROMISE_RESULT_CHANGE:
196                 cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Method '%s' invoked repairs", bp->name);
197                 break;
198 
199             case PROMISE_RESULT_FAIL:
200             case PROMISE_RESULT_DENIED:
201                 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Method '%s' failed in some repairs", bp->name);
202                 break;
203 
204             default: // PROMISE_RESULT_INTERRUPTED, TIMEOUT
205                 cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_FAIL, pp, a, "Method '%s' aborted in some repairs", bp->name);
206                 break;
207             }
208         }
209 
210         for (const Rlist *rp = bp->args; rp; rp = rp->next)
211         {
212             const char *lval = RlistScalarValue(rp);
213             VarRef *ref = VarRefParseFromBundle(lval, bp);
214             EvalContextVariableRemove(ctx, ref);
215             VarRefDestroy(ref);
216         }
217     }
218     else
219     {
220         if (IsCf3VarString(BufferData(method_name)))
221         {
222             Log(LOG_LEVEL_ERR,
223                 "A variable seems to have been used for the name of the method. In this case, the promiser also needs to contain the unique name of the method");
224         }
225 
226         cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
227              "A method attempted to use a bundle '%s' that was apparently not defined",
228              BufferData(method_name));
229         result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
230     }
231 
232     YieldCurrentLock(thislock);
233     BufferDestroy(method_name);
234     EndBundleBanner(bp);
235 
236     return result;
237 }
238 
239 /***********************************************************************/
240 
GetReturnValue(EvalContext * ctx,const Bundle * callee,const Promise * caller)241 static void GetReturnValue(EvalContext *ctx, const Bundle *callee, const Promise *caller)
242 {
243     char *result = PromiseGetConstraintAsRval(caller, "useresult", RVAL_TYPE_SCALAR);
244 
245     if (result)
246     {
247         VarRef *ref = VarRefParseFromBundle("last-result", callee);
248         VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, ref->ns, ref->scope, ref->lval);
249         Variable *result_var = NULL;
250         while ((result_var = VariableTableIteratorNext(iter)))
251         {
252             const VarRef *result_var_ref = VariableGetRef(result_var);
253             assert(result_var_ref->num_indices == 1);
254             if (result_var_ref->num_indices != 1)
255             {
256                 continue;
257             }
258 
259             VarRef *new_ref = VarRefParseFromBundle(result, PromiseGetBundle(caller));
260             VarRefAddIndex(new_ref, result_var_ref->indices[0]);
261 
262             Rval result_var_rval = VariableGetRval(result_var, true);
263             EvalContextVariablePut(ctx, new_ref, result_var_rval.item, VariableGetType(result_var), "source=bundle");
264 
265             VarRefDestroy(new_ref);
266         }
267 
268         VarRefDestroy(ref);
269         VariableTableIteratorDestroy(iter);
270     }
271 
272 }
273