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 <vars.h>
26 
27 #include <conversion.h>
28 #include <expand.h>
29 #include <scope.h>
30 #include <matching.h>
31 #include <unix.h>
32 #include <misc_lib.h>
33 #include <rlist.h>
34 #include <policy.h>
35 #include <eval_context.h>
36 
37 static bool IsCf3Scalar(char *str);
38 
39 /*******************************************************************/
40 
RlistIsUnresolved(const Rlist * list)41 bool RlistIsUnresolved(const Rlist *list)
42 {
43     for (const Rlist *rp = list; rp != NULL; rp = rp->next)
44     {
45         // JSON data container values are never expanded, except with the
46         // data_expand() function which see.
47         if (rp->val.type == RVAL_TYPE_CONTAINER)
48         {
49             continue;
50         }
51 
52         if (rp->val.type != RVAL_TYPE_SCALAR)
53         {
54             return true;
55         }
56 
57         if (IsCf3Scalar(RlistScalarValue(rp)))
58         {
59             if (strstr(RlistScalarValue(rp), "$(this)") || strstr(RlistScalarValue(rp), "${this}") ||
60                 strstr(RlistScalarValue(rp), "$(this.k)") || strstr(RlistScalarValue(rp), "${this.k}") ||
61                 strstr(RlistScalarValue(rp), "$(this.k[1])") || strstr(RlistScalarValue(rp), "${this.k[1]}") ||
62                 strstr(RlistScalarValue(rp), "$(this.v)") || strstr(RlistScalarValue(rp), "${this.v}"))
63             {
64                 // We should allow this in function args for substitution in maplist() etc
65                 // We should allow this.k and this.k[1] and this.v in function args for substitution in maparray() etc
66             }
67             else
68             {
69                 return true;
70             }
71         }
72     }
73 
74     return false;
75 }
76 
77 /******************************************************************/
78 
StringContainsVar(const char * s,const char * v)79 bool StringContainsVar(const char *s, const char *v)
80 {
81     int vlen = strlen(v);
82 
83     if (s == NULL)
84     {
85         return false;
86     }
87 
88 /* Look for ${v}, $(v), @{v}, $(v) */
89 
90     for (;;)
91     {
92         /* Look for next $ or @ */
93         s = strpbrk(s, "$@");
94         if (s == NULL)
95         {
96             return false;
97         }
98         /* If next symbol */
99         if (*++s == '\0')
100         {
101             return false;
102         }
103         /* is { or ( */
104         if (*s != '(' && *s != '{')
105         {
106             continue;
107         }
108         /* Then match the variable starting from next symbol */
109         if (strncmp(s + 1, v, vlen) != 0)
110         {
111             continue;
112         }
113         /* And if it matched, match the closing bracket */
114         if ((s[0] == '(' && s[vlen + 1] == ')') || (s[0] == '{' && s[vlen + 1] == '}'))
115         {
116             return true;
117         }
118     }
119 }
120 
121 /*********************************************************************/
122 
IsCf3VarString(const char * str)123 bool IsCf3VarString(const char *str)
124 {
125     char left = 'x', right = 'x';
126     int dollar = false;
127     int bracks = 0, vars = 0;
128 
129     if (str == NULL)
130     {
131         return false;
132     }
133 
134     for (const char *sp = str; *sp != '\0'; sp++)   /* check for varitems */
135     {
136         switch (*sp)
137         {
138         case '$':
139         case '@':
140             if (*(sp + 1) == '{' || *(sp + 1) == '(')
141             {
142                 dollar = true;
143             }
144             break;
145         case '(':
146         case '{':
147             if (dollar)
148             {
149                 left = *sp;
150                 bracks++;
151             }
152             break;
153         case ')':
154         case '}':
155             if (dollar)
156             {
157                 bracks--;
158                 right = *sp;
159             }
160             break;
161         }
162 
163         /* Some chars cannot be in variable ids, e.g.
164            $(/bin/cat file) is legal in bash */
165 
166         if ((bracks > 0) && (*sp == '/'))
167         {
168             return false;
169         }
170 
171         if (left == '(' && right == ')' && dollar && (bracks == 0))
172         {
173             vars++;
174             dollar = false;
175         }
176 
177         if (left == '{' && right == '}' && dollar && (bracks == 0))
178         {
179             vars++;
180             dollar = false;
181         }
182     }
183 
184     if (dollar && (bracks != 0))
185     {
186         char output[CF_BUFSIZE];
187 
188         snprintf(output, CF_BUFSIZE, "Broken variable syntax or bracket mismatch in string (%s)", str);
189         yyerror(output);
190         return false;
191     }
192 
193     return (vars != 0);
194 }
195 
196 /*********************************************************************/
197 
IsCf3Scalar(char * str)198 static bool IsCf3Scalar(char *str)
199 {
200     char *sp;
201     char left = 'x', right = 'x';
202     int dollar = false;
203     int bracks = 0, vars = 0;
204 
205     if (str == NULL)
206     {
207         return false;
208     }
209 
210     for (sp = str; *sp != '\0'; sp++)   /* check for varitems */
211     {
212         switch (*sp)
213         {
214         case '$':
215             if (*(sp + 1) == '{' || *(sp + 1) == '(')
216             {
217                 dollar = true;
218             }
219             break;
220         case '(':
221         case '{':
222             if (dollar)
223             {
224                 left = *sp;
225                 bracks++;
226             }
227             break;
228         case ')':
229         case '}':
230             if (dollar)
231             {
232                 bracks--;
233                 right = *sp;
234             }
235             break;
236         }
237 
238         /* Some chars cannot be in variable ids, e.g.
239            $(/bin/cat file) is legal in bash */
240 
241         if ((bracks > 0) && (*sp == '/'))
242         {
243             return false;
244         }
245 
246         if (left == '(' && right == ')' && dollar && (bracks == 0))
247         {
248             vars++;
249             dollar = false;
250         }
251 
252         if (left == '{' && right == '}' && dollar && (bracks == 0))
253         {
254             vars++;
255             dollar = false;
256         }
257     }
258 
259     if (dollar && (bracks != 0))
260     {
261         char output[CF_BUFSIZE];
262 
263         snprintf(output, CF_BUFSIZE, "Broken scalar variable syntax or bracket mismatch in '%s'", str);
264         yyerror(output);
265         return false;
266     }
267 
268     return (vars != 0);
269 }
270 
271 /* Extract everything up to the dollar sign. */
ExtractScalarPrefix(Buffer * out,const char * str,size_t len)272 size_t ExtractScalarPrefix(Buffer *out, const char *str, size_t len)
273 {
274     assert(str);
275     if (len == 0)
276     {
277         return 0;
278     }
279 
280     const char *dollar_point = NULL;
281     for (size_t i = 0; i < (len - 1); i++)
282     {
283         if (str[i] == '$')
284         {
285             if (str[i + 1] == '(' || str[i + 1] == '{')
286             {
287                 dollar_point = str + i;
288                 break;
289             }
290         }
291     }
292 
293     if (!dollar_point)
294     {
295         BufferAppend(out, str, len);
296         return len;
297     }
298     else if (dollar_point > str)
299     {
300         size_t prefix_len = dollar_point - str;
301         if (prefix_len > 0)
302         {
303             BufferAppend(out, str, prefix_len);
304         }
305         return prefix_len;
306     }
307     return 0;
308 }
309 
ReferenceEnd(const char * str,size_t len)310 static const char *ReferenceEnd(const char *str, size_t len)
311 {
312     assert(len > 1);
313     assert(str[0] == '$');
314     assert(str[1] == '{' || str[1] == '(');
315 
316 #define MAX_VARIABLE_REFERENCE_LEVELS 10
317     char stack[MAX_VARIABLE_REFERENCE_LEVELS] = { 0, str[1], 0 };
318     int level = 1;
319 
320     for (size_t i = 2; i < len; i++)
321     {
322         switch (str[i])
323         {
324         case '{':
325         case '(':
326             if (level < MAX_VARIABLE_REFERENCE_LEVELS - 1)
327             {
328                 level++;
329                 stack[level] = str[i];
330             }
331             else
332             {
333                 Log(LOG_LEVEL_ERR, "Stack overflow in variable reference parsing. More than %d levels", MAX_VARIABLE_REFERENCE_LEVELS);
334                 return NULL;
335             }
336             break;
337 
338         case '}':
339             if (stack[level] != '{')
340             {
341                 Log(LOG_LEVEL_ERR, "Variable reference bracket mismatch '%.*s'",
342                     (int) len, str);
343                 return NULL;
344             }
345             level--;
346             break;
347         case ')':
348             if (stack[level] != '(')
349             {
350                 Log(LOG_LEVEL_ERR, "Variable reference bracket mismatch '%.*s'",
351                     (int) len, str);
352                 return NULL;
353             }
354             level--;
355             break;
356         }
357 
358         if (level == 0)
359         {
360             return str + i;
361         }
362     }
363 
364     return NULL;
365 }
366 
367 /**
368  * Extract variable inside dollar-paren.
369  * @param extract_inner ignore opening dollar-paren and closing paren.
370  */
ExtractScalarReference(Buffer * out,const char * str,size_t len,bool extract_inner)371 bool ExtractScalarReference(Buffer *out, const char *str, size_t len, bool extract_inner)
372 {
373     if (len <= 1)
374     {
375         return false;
376     }
377 
378     const char *dollar_point = memchr(str, '$', len);
379     if (!dollar_point || (size_t) (dollar_point - str) == len)
380     {
381         return false;
382     }
383     else
384     {
385         const char *close_point = NULL;
386         {
387             size_t remaining = len - (dollar_point - str);
388             if (*(dollar_point + 1) == '{' || *(dollar_point + 1) == '(')
389             {
390                 close_point = ReferenceEnd(dollar_point, remaining);
391             }
392             else
393             {
394                 return ExtractScalarReference(out, dollar_point + 1,
395                                               remaining - 1, extract_inner);
396             }
397         }
398 
399         if (!close_point)
400         {
401             Log(LOG_LEVEL_ERR, "Variable reference close mismatch '%.*s'",
402                 (int) len, str);
403             return false;
404         }
405 
406         size_t outer_len = close_point - dollar_point + 1;
407         if (outer_len <= 3)
408         {
409             Log(LOG_LEVEL_ERR, "Empty variable reference close mismatch '%.*s'",
410                 (int) len, str);
411             return false;
412         }
413 
414         if (extract_inner)
415         {
416             BufferAppend(out, dollar_point + 2, outer_len - 3);
417         }
418         else
419         {
420             BufferAppend(out, dollar_point, outer_len);
421         }
422         return true;
423     }
424 }
425 
426 /*********************************************************************/
427 
IsQualifiedVariable(const char * var)428 bool IsQualifiedVariable(const char *var)
429 {
430     int isarraykey = false;
431 
432     for (const char *sp = var; *sp != '\0'; sp++)
433     {
434         if (*sp == '[')
435         {
436             isarraykey = true;
437         }
438 
439         if (isarraykey)
440         {
441             return false;
442         }
443         else
444         {
445             if (*sp == '.')
446             {
447                 return true;
448             }
449         }
450     }
451 
452     return false;
453 }
454