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