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 /*
26   This file is an attempt to clean up cf3parse.y, moving as much
27   C code (logic) as possible out of it, so the actual grammar
28   is more readable. It should only be included from cf3parse.y (!).
29 
30   The advantages of moving the C code out of the grammar are:
31   * Separate overall grammar from noisy details
32   * Less crazy indentation
33   * Better editor support for auto complete / syntax highlighting
34 */
35 
36 #ifndef CF3_PARSE_LOGIC_H
37 #define CF3_PARSE_LOGIC_H
38 
39 #include "cf3.defs.h"
40 #include "parser.h"
41 #include "parser_helpers.h"
42 #include "parser_state.h"
43 
44 #include "logging.h"
45 #include "fncall.h"
46 #include "rlist.h"
47 #include "item_lib.h"
48 #include "policy.h"
49 #include "mod_files.h"
50 #include "string_lib.h"
51 #include "logic_expressions.h"
52 #include "json-yaml.h"
53 #include "cleanup.h"
54 
55 // FIX: remove
56 #include "syntax.h"
57 
58 #include <assert.h>
59 
60 int yylex(void);
61 extern char *yytext;
62 
63 static bool RelevantBundle(const char *agent, const char *blocktype);
64 static bool LvalWantsBody(char *stype, char *lval);
65 static SyntaxTypeMatch CheckSelection(
66     ParserBlock block,
67     const char *type,
68     const char *name,
69     const char *lval,
70     Rval rval);
71 static SyntaxTypeMatch CheckConstraint(
72     const char *type,
73     const char *lval,
74     Rval rval,
75     const PromiseTypeSyntax *ss);
76 static void fatal_yyerror(const char *s);
77 
78 static void ParseErrorColumnOffset(int column_offset, const char *s, ...)
79     FUNC_ATTR_PRINTF(2, 3);
80 static void ParseError(const char *s, ...) FUNC_ATTR_PRINTF(1, 2);
81 static void ParseWarning(unsigned int warning, const char *s, ...)
82     FUNC_ATTR_PRINTF(2, 3);
83 
84 static void ValidateClassLiteral(const char *class_literal);
85 
86 static bool INSTALL_SKIP = false;
87 static size_t CURRENT_BLOCKID_LINE = 0;
88 static size_t CURRENT_PROMISER_LINE = 0;
89 
90 #define YYMALLOC xmalloc
91 #define P PARSER_STATE
92 
93 #define ParserDebug(...) LogDebug(LOG_MOD_PARSER, __VA_ARGS__)
94 
95 
96 /*****************************************************************/
97 
ParseErrorVColumnOffset(int column_offset,const char * s,va_list ap)98 static void ParseErrorVColumnOffset(
99     int column_offset, const char *s, va_list ap)
100 {
101     char *errmsg = StringVFormat(s, ap);
102     fprintf(
103         stderr,
104         "%s:%d:%d: error: %s\n",
105         P.filename,
106         P.line_no,
107         P.line_pos + column_offset,
108         errmsg);
109     free(errmsg);
110 
111     P.error_count++;
112 
113     /* Current line is not set when syntax error in first line */
114     if (P.current_line)
115     {
116         fprintf(stderr, "%s\n", P.current_line);
117         fprintf(stderr, "%*s\n", P.line_pos + column_offset, "^");
118     }
119 
120     if (P.error_count > 12)
121     {
122         fprintf(stderr, "Too many errors\n");
123         DoCleanupAndExit(EXIT_FAILURE);
124     }
125 }
126 
ParseErrorColumnOffset(int column_offset,const char * s,...)127 static void ParseErrorColumnOffset(int column_offset, const char *s, ...)
128 {
129     va_list ap;
130     va_start(ap, s);
131     ParseErrorVColumnOffset(column_offset, s, ap);
132     va_end(ap);
133 }
134 
ParseErrorV(const char * s,va_list ap)135 static void ParseErrorV(const char *s, va_list ap)
136 {
137     ParseErrorVColumnOffset(0, s, ap);
138 }
139 
ParseError(const char * s,...)140 static void ParseError(const char *s, ...)
141 {
142     va_list ap;
143     va_start(ap, s);
144     ParseErrorV(s, ap);
145     va_end(ap);
146 }
147 
ParseWarningV(unsigned int warning,const char * s,va_list ap)148 static void ParseWarningV(unsigned int warning, const char *s, va_list ap)
149 {
150     if (((P.warnings | P.warnings_error) & warning) == 0)
151     {
152         return;
153     }
154 
155     char *errmsg = StringVFormat(s, ap);
156     const char *warning_str = ParserWarningToString(warning);
157 
158     fprintf(
159         stderr,
160         "%s:%d:%d: warning: %s [-W%s]\n",
161         P.filename,
162         P.line_no,
163         P.line_pos,
164         errmsg,
165         warning_str);
166     fprintf(stderr, "%s\n", P.current_line);
167     fprintf(stderr, "%*s\n", P.line_pos, "^");
168 
169     free(errmsg);
170 
171     P.warning_count++;
172 
173     if ((P.warnings_error & warning) != 0)
174     {
175         P.error_count++;
176     }
177 
178     if (P.error_count > 12)
179     {
180         fprintf(stderr, "Too many errors\n");
181         DoCleanupAndExit(EXIT_FAILURE);
182     }
183 }
184 
ParseWarning(unsigned int warning,const char * s,...)185 static void ParseWarning(unsigned int warning, const char *s, ...)
186 {
187     va_list ap;
188     va_start(ap, s);
189     ParseWarningV(warning, s, ap);
190     va_end(ap);
191 }
192 
yyerror(const char * str)193 void yyerror(const char *str)
194 {
195     ParseError("%s", str);
196 }
197 
fatal_yyerror(const char * s)198 static void fatal_yyerror(const char *s)
199 {
200     char *sp = yytext;
201     /* Skip quotation mark */
202     if (sp && *sp == '\"' && sp[1])
203     {
204         sp++;
205     }
206 
207     fprintf(
208         stderr,
209         "%s: %d,%d: Fatal error during parsing: %s, near token \'%.20s\'\n",
210         P.filename,
211         P.line_no,
212         P.line_pos,
213         s,
214         sp ? sp : "NULL");
215     DoCleanupAndExit(EXIT_FAILURE);
216 }
217 
RelevantBundle(const char * agent,const char * blocktype)218 static bool RelevantBundle(const char *agent, const char *blocktype)
219 {
220     if ((strcmp(agent, CF_AGENTTYPES[AGENT_TYPE_COMMON]) == 0)
221         || (strcmp(CF_COMMONC, blocktype) == 0))
222     {
223         return true;
224     }
225 
226     /* Here are some additional bundle types handled by cfAgent */
227 
228     Item *ip = SplitString("edit_line,edit_xml", ',');
229 
230     if (strcmp(agent, CF_AGENTTYPES[AGENT_TYPE_AGENT]) == 0)
231     {
232         if (IsItemIn(ip, blocktype))
233         {
234             DeleteItemList(ip);
235             return true;
236         }
237     }
238 
239     DeleteItemList(ip);
240     return false;
241 }
242 
LvalWantsBody(char * stype,char * lval)243 static bool LvalWantsBody(char *stype, char *lval)
244 {
245     for (int i = 0; i < CF3_MODULES; i++)
246     {
247         const PromiseTypeSyntax *promise_type_syntax = CF_ALL_PROMISE_TYPES[i];
248         if (!promise_type_syntax)
249         {
250             continue;
251         }
252 
253         for (int j = 0; promise_type_syntax[j].promise_type != NULL; j++)
254         {
255             const ConstraintSyntax *bs = promise_type_syntax[j].constraints;
256             if (!bs)
257             {
258                 continue;
259             }
260 
261             if (strcmp(promise_type_syntax[j].promise_type, stype) != 0)
262             {
263                 continue;
264             }
265 
266             for (int l = 0; bs[l].lval != NULL; l++)
267             {
268                 if (strcmp(bs[l].lval, lval) == 0)
269                 {
270                     if (bs[l].dtype == CF_DATA_TYPE_BODY)
271                     {
272                         return true;
273                     }
274                     else
275                     {
276                         return false;
277                     }
278                 }
279             }
280         }
281     }
282 
283     return false;
284 }
285 
CheckSelection(ParserBlock block,const char * type,const char * name,const char * lval,Rval rval)286 static SyntaxTypeMatch CheckSelection(
287     ParserBlock block,
288     const char *type,
289     const char *name,
290     const char *lval,
291     Rval rval)
292 {
293     if (block == PARSER_BLOCK_PROMISE)
294     {
295         const BodySyntax *body_syntax = BodySyntaxGet(block, type);
296         const ConstraintSyntax *constraints = body_syntax->constraints;
297         for (int i = 0; constraints[i].lval != NULL; i++)
298         {
299             if (StringEqual(lval, constraints[i].lval))
300             {
301                 return CheckConstraintTypeMatch(
302                     lval,
303                     rval,
304                     constraints[i].dtype,
305                     constraints[i].range.validation_string,
306                     0);
307             }
308         }
309         // Parser should ensure that lval is a valid
310         // attribute, and so we should never get here
311         debug_abort_if_reached();
312     }
313 
314     assert(block == PARSER_BLOCK_BODY);
315 
316     // Check internal control bodies etc
317     if (strcmp("control", name) == 0)
318     {
319         for (int i = 0; CONTROL_BODIES[i].body_type != NULL; i++)
320         {
321             if (strcmp(type, CONTROL_BODIES[i].body_type) == 0)
322             {
323                 const ConstraintSyntax *bs = CONTROL_BODIES[i].constraints;
324 
325                 for (int l = 0; bs[l].lval != NULL; l++)
326                 {
327                     if (strcmp(lval, bs[l].lval) == 0)
328                     {
329                         if (bs[l].dtype == CF_DATA_TYPE_BODY)
330                         {
331                             return SYNTAX_TYPE_MATCH_OK;
332                         }
333                         else if (bs[l].dtype == CF_DATA_TYPE_BUNDLE)
334                         {
335                             return SYNTAX_TYPE_MATCH_OK;
336                         }
337                         else
338                         {
339                             return CheckConstraintTypeMatch(
340                                 lval,
341                                 rval,
342                                 bs[l].dtype,
343                                 bs[l].range.validation_string,
344                                 0);
345                         }
346                     }
347                 }
348             }
349         }
350     }
351 
352     // Now check the functional modules - extra level of indirection
353     for (int i = 0; i < CF3_MODULES; i++)
354     {
355         const PromiseTypeSyntax *promise_type_syntax = CF_ALL_PROMISE_TYPES[i];
356         if (!promise_type_syntax)
357         {
358             continue;
359         }
360 
361         for (int j = 0; promise_type_syntax[j].promise_type != NULL; j++)
362         {
363             const ConstraintSyntax *bs = promise_type_syntax[j].constraints;
364 
365             if (!bs)
366             {
367                 continue;
368             }
369 
370             for (int l = 0; bs[l].lval != NULL; l++)
371             {
372                 if (bs[l].dtype == CF_DATA_TYPE_BODY)
373                 {
374                     const ConstraintSyntax *bs2 =
375                         bs[l].range.body_type_syntax->constraints;
376 
377                     if (bs2 == NULL || bs2 == (void *) CF_BUNDLE)
378                     {
379                         continue;
380                     }
381 
382                     for (int k = 0; bs2[k].dtype != CF_DATA_TYPE_NONE; k++)
383                     {
384                         /* Either module defined or common */
385 
386                         if (strcmp(promise_type_syntax[j].promise_type, type)
387                                 == 0
388                             && strcmp(promise_type_syntax[j].promise_type, "*")
389                                    != 0)
390                         {
391                             char output[CF_BUFSIZE];
392                             snprintf(
393                                 output,
394                                 CF_BUFSIZE,
395                                 "lval %s belongs to promise type '%s': but this is '%s'\n",
396                                 lval,
397                                 promise_type_syntax[j].promise_type,
398                                 type);
399                             yyerror(output);
400                             return SYNTAX_TYPE_MATCH_OK;
401                         }
402 
403                         if (strcmp(lval, bs2[k].lval) == 0)
404                         {
405                             /* Body definitions will be checked later. */
406                             if (bs2[k].dtype != CF_DATA_TYPE_BODY)
407                             {
408                                 return CheckConstraintTypeMatch(
409                                     lval,
410                                     rval,
411                                     bs2[k].dtype,
412                                     bs2[k].range.validation_string,
413                                     0);
414                             }
415                             else
416                             {
417                                 return SYNTAX_TYPE_MATCH_OK;
418                             }
419                         }
420                     }
421                 }
422             }
423         }
424     }
425 
426     char output[CF_BUFSIZE];
427     snprintf(
428         output,
429         CF_BUFSIZE,
430         "Constraint lvalue \"%s\" is not allowed in \'%s\' constraint body",
431         lval,
432         type);
433     yyerror(output);
434 
435     return SYNTAX_TYPE_MATCH_OK; // TODO: OK?
436 }
437 
CheckConstraint(const char * type,const char * lval,Rval rval,const PromiseTypeSyntax * promise_type_syntax)438 static SyntaxTypeMatch CheckConstraint(
439     const char *type,
440     const char *lval,
441     Rval rval,
442     const PromiseTypeSyntax *promise_type_syntax)
443 {
444     assert(promise_type_syntax);
445 
446     if (promise_type_syntax->promise_type != NULL) /* In a bundle */
447     {
448         if (strcmp(promise_type_syntax->promise_type, type) == 0)
449         {
450             const ConstraintSyntax *bs = promise_type_syntax->constraints;
451 
452             for (int l = 0; bs[l].lval != NULL; l++)
453             {
454                 if (strcmp(lval, bs[l].lval) == 0)
455                 {
456                     /* If we get here we have found the lval and it is valid
457                        for this promise_type */
458 
459                     /* For bodies and bundles definitions can be elsewhere, so
460                        they are checked in PolicyCheckRunnable(). */
461                     if (bs[l].dtype != CF_DATA_TYPE_BODY
462                         && bs[l].dtype != CF_DATA_TYPE_BUNDLE)
463                     {
464                         return CheckConstraintTypeMatch(
465                             lval,
466                             rval,
467                             bs[l].dtype,
468                             bs[l].range.validation_string,
469                             0);
470                     }
471                 }
472             }
473         }
474     }
475 
476     return SYNTAX_TYPE_MATCH_OK;
477 }
478 
ValidateClassLiteral(const char * class_literal)479 static void ValidateClassLiteral(const char *class_literal)
480 {
481     ParseResult res = ParseExpression(class_literal, 0, strlen(class_literal));
482 
483     if (!res.result)
484     {
485         ParseErrorColumnOffset(
486             res.position - strlen(class_literal),
487             "Syntax error in context string");
488     }
489 
490     FreeExpression(res.result);
491 }
492 
ParserEndCurrentBlock()493 static inline void ParserEndCurrentBlock()
494 {
495     P.offsets.last_id = -1;
496     P.offsets.last_string = -1;
497     P.offsets.last_class_id = -1;
498     if (P.block != PARSER_BLOCK_BUNDLE && P.currentbody != NULL)
499     {
500         P.currentbody->offset.end = P.offsets.current;
501     }
502 
503     if (P.block == PARSER_BLOCK_BUNDLE && P.currentbundle != NULL)
504     {
505         P.currentbundle->offset.end = P.offsets.current;
506     }
507 }
508 
509 // This part of the parser is interesting, to say the least.
510 // While parsing, we try to, opportunistically, do a bunch of
511 // transformations on the right-hand side values (rval) of
512 // promise attributes. For example converting some patterns
513 // to function calls:
514 // @(x)
515 //   mergedata(x)
516 // data => "{}"
517 //   data => parsejson("{}")
518 //
519 // In some cases, we even try to evaluate the function call,
520 // like parsejson, and if it succeeds (if there are no
521 // unresolved variables) we just insert the resulting data
522 // container directly.
523 
MagicRvalTransformations(const PromiseTypeSyntax * promise_type_syntax)524 static inline void MagicRvalTransformations(
525     const PromiseTypeSyntax *promise_type_syntax)
526 {
527     const char *item = P.rval.item;
528     // convert @(x) to mergedata(x)
529     if (P.rval.type == RVAL_TYPE_SCALAR
530         && (strcmp(P.lval, "data") == 0
531             || strcmp(P.lval, "template_data") == 0)
532         && strlen(item) > 3 && item[0] == '@'
533         && (item[1] == '(' || item[1] == '{'))
534     {
535         Rlist *synthetic_args = NULL;
536         char *tmp = xstrndup(P.rval.item + 2, strlen(P.rval.item) - 3);
537         RlistAppendScalar(&synthetic_args, tmp);
538         free(tmp);
539         RvalDestroy(P.rval);
540 
541         P.rval =
542             (Rval){FnCallNew("mergedata", synthetic_args), RVAL_TYPE_FNCALL};
543     }
544     // convert 'json or yaml' to direct container or parsejson(x)
545     // or parseyaml(x)
546     else if (
547         P.rval.type == RVAL_TYPE_SCALAR
548         && (strcmp(P.lval, "data") == 0
549             || strcmp(P.lval, "template_data") == 0))
550     {
551         JsonElement *json = NULL;
552         JsonParseError res;
553         bool json_parse_attempted = false;
554         Buffer *copy = BufferNewFrom(P.rval.item, strlen(P.rval.item));
555 
556         const char *fname = NULL;
557         if (strlen(P.rval.item) > 3 && strncmp("---", P.rval.item, 3) == 0)
558         {
559             fname = "parseyaml";
560 
561             // look for unexpanded variables
562             if (strstr(P.rval.item, "$(") == NULL
563                 && strstr(P.rval.item, "${") == NULL)
564             {
565                 const char *copy_data = BufferData(copy);
566                 res = JsonParseYamlString(&copy_data, &json);
567                 json_parse_attempted = true;
568             }
569         }
570         else
571         {
572             fname = "parsejson";
573             // look for unexpanded variables
574             if (strstr(P.rval.item, "$(") == NULL
575                 && strstr(P.rval.item, "${") == NULL)
576             {
577                 const char *copy_data = BufferData(copy);
578                 res = JsonParse(&copy_data, &json);
579                 json_parse_attempted = true;
580             }
581         }
582 
583         BufferDestroy(copy);
584 
585         if (json_parse_attempted && res != JSON_PARSE_OK)
586         {
587             // Parsing failed, insert fncall so it can be retried
588             // during evaluation
589         }
590         else if (
591             json != NULL
592             && JsonGetElementType(json) == JSON_ELEMENT_TYPE_PRIMITIVE)
593         {
594             // Parsing failed, insert fncall so it can be retried
595             // during evaluation
596             JsonDestroy(json);
597             json = NULL;
598         }
599 
600         if (fname != NULL)
601         {
602             if (json == NULL)
603             {
604                 Rlist *synthetic_args = NULL;
605                 RlistAppendScalar(&synthetic_args, P.rval.item);
606                 RvalDestroy(P.rval);
607 
608                 P.rval =
609                     (Rval){FnCallNew(fname, synthetic_args), RVAL_TYPE_FNCALL};
610             }
611             else
612             {
613                 RvalDestroy(P.rval);
614                 P.rval = (Rval){json, RVAL_TYPE_CONTAINER};
615             }
616         }
617     }
618 
619     if (promise_type_syntax != NULL) // NULL for custom promise types
620     {
621         SyntaxTypeMatch err = CheckConstraint(
622             P.currenttype, P.lval, P.rval, promise_type_syntax);
623         if (err != SYNTAX_TYPE_MATCH_OK
624             && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
625         {
626             yyerror(SyntaxTypeMatchToString(err));
627         }
628     }
629 
630     if (P.rval.type == RVAL_TYPE_SCALAR
631         && (strcmp(P.lval, "ifvarclass") == 0 || strcmp(P.lval, "if") == 0))
632     {
633         ValidateClassLiteral(P.rval.item);
634     }
635 }
636 
ParserAppendCurrentConstraint()637 static inline void ParserAppendCurrentConstraint()
638 {
639     Constraint *cp = PromiseAppendConstraint(
640         P.currentpromise, P.lval, RvalCopy(P.rval), P.references_body);
641     cp->offset.line = P.line_no;
642     cp->offset.start = P.offsets.last_id;
643     cp->offset.end = P.offsets.current;
644     cp->offset.context = P.offsets.last_class_id;
645     P.currentstype->offset.end = P.offsets.current;
646 }
647 
648 // This function is called for every rval (right hand side value) of every
649 // promise while parsing. The reason why it is so big is because it does a lot
650 // of transformation, for example transforming strings into function calls like
651 // parsejson, and then attempts to resolve those function calls.
ParserHandleBundlePromiseRval()652 static inline void ParserHandleBundlePromiseRval()
653 {
654     if (INSTALL_SKIP)
655     {
656         RvalDestroy(P.rval);
657         P.rval = RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
658         return;
659     }
660 
661     if (PolicyHasCustomPromiseType(P.policy, P.currenttype))
662     {
663         // Definitely custom promise type, just add the constraint and move on
664         MagicRvalTransformations(NULL);
665         ParserAppendCurrentConstraint();
666         goto cleanup;
667     }
668 
669     const ConstraintSyntax *constraint_syntax = NULL;
670     const PromiseTypeSyntax *promise_type_syntax =
671         PromiseTypeSyntaxGet(P.blocktype, P.currenttype);
672     if (promise_type_syntax != NULL)
673     {
674         constraint_syntax =
675             PromiseTypeSyntaxGetConstraintSyntax(promise_type_syntax, P.lval);
676     }
677 
678     if (promise_type_syntax == NULL)
679     {
680         // Assume custom promise type, but defined in another policy file
681         MagicRvalTransformations(NULL);
682         ParserAppendCurrentConstraint();
683         goto cleanup;
684     }
685     else if (constraint_syntax == NULL)
686     {
687         ParseError(
688             "Unknown constraint '%s' in promise type '%s'",
689             P.lval,
690             promise_type_syntax->promise_type);
691     }
692     else
693     {
694         switch (constraint_syntax->status)
695         {
696         case SYNTAX_STATUS_DEPRECATED:
697             ParseWarning(
698                 PARSER_WARNING_DEPRECATED,
699                 "Deprecated constraint '%s' in promise type '%s'",
700                 constraint_syntax->lval,
701                 promise_type_syntax->promise_type);
702             // fall through
703         case SYNTAX_STATUS_NORMAL:
704         {
705             MagicRvalTransformations(promise_type_syntax);
706             ParserAppendCurrentConstraint();
707         }
708         break;
709         case SYNTAX_STATUS_REMOVED:
710             ParseWarning(
711                 PARSER_WARNING_REMOVED,
712                 "Removed constraint '%s' in promise type '%s'",
713                 constraint_syntax->lval,
714                 promise_type_syntax->promise_type);
715             break;
716         }
717     }
718 
719 cleanup:
720 
721     RvalDestroy(P.rval);
722     P.rval = RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
723     strcpy(P.lval, "no lval");
724     RlistDestroy(P.currentRlist);
725     P.currentRlist = NULL;
726 }
727 
ParserBeginBlock(ParserBlock b)728 static inline void ParserBeginBlock(ParserBlock b)
729 {
730     ParserDebug("P:%s:%s\n", ParserBlockString(b), P.blocktype);
731     P.block = b;
732 
733     if (b == PARSER_BLOCK_BUNDLE)
734     {
735         RvalDestroy(P.rval);
736         P.rval = RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
737     }
738 
739     RlistDestroy(P.currentRlist);
740     P.currentRlist = NULL;
741 
742     if (P.currentstring)
743     {
744         free(P.currentstring);
745     }
746     P.currentstring = NULL;
747 
748     strcpy(P.blockid, "");
749 }
750 
751 // The promise "guard" is a promise type followed by a single colon,
752 // found in bundles. It is called guard because it resembles the
753 // class guards, and all other names I could think of were confusing.
754 // (It doesn't really "guard" anything).
ParserHandlePromiseGuard()755 static inline void ParserHandlePromiseGuard()
756 {
757     ParserDebug(
758         "\tP:%s:%s:%s promise_type = %s\n",
759         ParserBlockString(P.block),
760         P.blocktype,
761         P.blockid,
762         P.currenttype);
763 
764     const PromiseTypeSyntax *promise_type_syntax =
765         PromiseTypeSyntaxGet(P.blocktype, P.currenttype);
766 
767     if (promise_type_syntax)
768     {
769         switch (promise_type_syntax->status)
770         {
771         case SYNTAX_STATUS_DEPRECATED:
772             ParseWarning(
773                 PARSER_WARNING_DEPRECATED,
774                 "Deprecated promise type '%s' in bundle type '%s'",
775                 promise_type_syntax->promise_type,
776                 promise_type_syntax->bundle_type);
777             // fall through
778         case SYNTAX_STATUS_NORMAL:
779             if (P.block == PARSER_BLOCK_BUNDLE)
780             {
781                 if (!INSTALL_SKIP)
782                 {
783                     P.currentstype =
784                         BundleAppendSection(P.currentbundle, P.currenttype);
785                     P.currentstype->offset.line = P.line_no;
786                     P.currentstype->offset.start =
787                         P.offsets.last_promise_guard_id;
788                 }
789                 else
790                 {
791                     P.currentstype = NULL;
792                 }
793             }
794             break;
795         case SYNTAX_STATUS_REMOVED:
796             ParseWarning(
797                 PARSER_WARNING_REMOVED,
798                 "Removed promise type '%s' in bundle type '%s'",
799                 promise_type_syntax->promise_type,
800                 promise_type_syntax->bundle_type);
801             INSTALL_SKIP = true;
802             break;
803         }
804     }
805     else
806     {
807         // Unrecognized promise type, assume it is custom
808         // no way to know while parsing, let's check later:
809         if (!INSTALL_SKIP)
810         {
811             P.currentstype =
812                 BundleAppendSection(P.currentbundle, P.currenttype);
813             P.currentstype->offset.line = P.line_no;
814             P.currentstype->offset.start = P.offsets.last_promise_guard_id;
815         }
816         else
817         {
818             P.currentstype = NULL;
819         }
820     }
821 }
822 
823 // Called at the beginning of the body of the block, i.e. the opening '{'
ParserBeginBlockBody()824 static inline void ParserBeginBlockBody()
825 {
826     const BodySyntax *body_syntax = BodySyntaxGet(P.block, P.blocktype);
827 
828     if (P.block == PARSER_BLOCK_PROMISE && body_syntax != NULL)
829     {
830         P.currentbody = PolicyAppendPromiseBlock(
831             P.policy,
832             P.current_namespace,
833             P.blockid,
834             P.blocktype,
835             P.useargs,
836             P.filename);
837         P.currentbody->offset.line = CURRENT_BLOCKID_LINE;
838         P.currentbody->offset.start = P.offsets.last_block_id;
839     }
840     else if (body_syntax)
841     {
842         INSTALL_SKIP = false;
843 
844         switch (body_syntax->status)
845         {
846         case SYNTAX_STATUS_DEPRECATED:
847             ParseWarning(
848                 PARSER_WARNING_DEPRECATED,
849                 "Deprecated body '%s' of type '%s'",
850                 P.blockid,
851                 body_syntax->body_type);
852             // fall through
853         case SYNTAX_STATUS_NORMAL:
854             P.currentbody = PolicyAppendBody(
855                 P.policy,
856                 P.current_namespace,
857                 P.blockid,
858                 P.blocktype,
859                 P.useargs,
860                 P.filename);
861             P.currentbody->offset.line = CURRENT_BLOCKID_LINE;
862             P.currentbody->offset.start = P.offsets.last_block_id;
863             break;
864 
865         case SYNTAX_STATUS_REMOVED:
866             ParseWarning(
867                 PARSER_WARNING_REMOVED,
868                 "Removed body '%s' of type '%s'",
869                 P.blockid,
870                 body_syntax->body_type);
871             INSTALL_SKIP = true;
872             break;
873         }
874     }
875     else
876     {
877         ParseError("Invalid body type '%s'", P.blocktype);
878         INSTALL_SKIP = true;
879     }
880 
881     RlistDestroy(P.useargs);
882     P.useargs = NULL;
883 
884     strcpy(P.currentid, "");
885 }
886 
887 // Called for every Rval (Right hand side value) of attributes
888 // in body blocks
ParserHandleBlockAttributeRval()889 static inline void ParserHandleBlockAttributeRval()
890 {
891     assert(P.block == PARSER_BLOCK_BODY || P.block == PARSER_BLOCK_PROMISE);
892 
893     if (!INSTALL_SKIP)
894     {
895         const BodySyntax *body_syntax = BodySyntaxGet(P.block, P.blocktype);
896         assert(body_syntax != NULL);
897 
898         const ConstraintSyntax *constraint_syntax =
899             BodySyntaxGetConstraintSyntax(body_syntax->constraints, P.lval);
900         if (constraint_syntax)
901         {
902             switch (constraint_syntax->status)
903             {
904             case SYNTAX_STATUS_DEPRECATED:
905                 ParseWarning(
906                     PARSER_WARNING_DEPRECATED,
907                     "Deprecated constraint '%s' in body type '%s'",
908                     constraint_syntax->lval,
909                     body_syntax->body_type);
910                 // fall through
911             case SYNTAX_STATUS_NORMAL:
912             {
913                 SyntaxTypeMatch err = CheckSelection(
914                     P.block, P.blocktype, P.blockid, P.lval, P.rval);
915                 if (err != SYNTAX_TYPE_MATCH_OK
916                     && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
917                 {
918                     yyerror(SyntaxTypeMatchToString(err));
919                 }
920 
921                 if (P.rval.type == RVAL_TYPE_SCALAR
922                     && (strcmp(P.lval, "ifvarclass") == 0
923                         || strcmp(P.lval, "if") == 0))
924                 {
925                     ValidateClassLiteral(P.rval.item);
926                 }
927 
928                 Constraint *cp = NULL;
929                 if (P.currentclasses == NULL)
930                 {
931                     cp = BodyAppendConstraint(
932                         P.currentbody,
933                         P.lval,
934                         RvalCopy(P.rval),
935                         "any",
936                         P.references_body);
937                 }
938                 else
939                 {
940                     cp = BodyAppendConstraint(
941                         P.currentbody,
942                         P.lval,
943                         RvalCopy(P.rval),
944                         P.currentclasses,
945                         P.references_body);
946                 }
947 
948                 if (P.currentvarclasses != NULL)
949                 {
950                     ParseError(
951                         "Body attributes can't be put under a variable class '%s'",
952                         P.currentvarclasses);
953                 }
954 
955                 cp->offset.line = P.line_no;
956                 cp->offset.start = P.offsets.last_id;
957                 cp->offset.end = P.offsets.current;
958                 cp->offset.context = P.offsets.last_class_id;
959                 break;
960             }
961             case SYNTAX_STATUS_REMOVED:
962                 ParseWarning(
963                     PARSER_WARNING_REMOVED,
964                     "Removed constraint '%s' in promise type '%s'",
965                     constraint_syntax->lval,
966                     body_syntax->body_type);
967                 break;
968             }
969         }
970     }
971     else
972     {
973         RvalDestroy(P.rval);
974         P.rval = RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
975     }
976 
977     if (strcmp(P.blockid, "control") == 0 && strcmp(P.blocktype, "file") == 0)
978     {
979         if (strcmp(P.lval, "namespace") == 0)
980         {
981             if (P.rval.type != RVAL_TYPE_SCALAR)
982             {
983                 yyerror("namespace must be a constant scalar string");
984             }
985             else
986             {
987                 free(P.current_namespace);
988                 P.current_namespace = xstrdup(P.rval.item);
989             }
990         }
991     }
992 
993     RvalDestroy(P.rval);
994     P.rval = RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
995 }
996 
ParserBeginBundleBody()997 static inline void ParserBeginBundleBody()
998 {
999     assert(P.block == PARSER_BLOCK_BUNDLE);
1000 
1001     if (RelevantBundle(CF_AGENTTYPES[P.agent_type], P.blocktype))
1002     {
1003         INSTALL_SKIP = false;
1004     }
1005     else if (strcmp(CF_AGENTTYPES[P.agent_type], P.blocktype) != 0)
1006     {
1007         INSTALL_SKIP = true;
1008     }
1009 
1010     if (!INSTALL_SKIP)
1011     {
1012         P.currentbundle = PolicyAppendBundle(
1013             P.policy,
1014             P.current_namespace,
1015             P.blockid,
1016             P.blocktype,
1017             P.useargs,
1018             P.filename);
1019         P.currentbundle->offset.line = CURRENT_BLOCKID_LINE;
1020         P.currentbundle->offset.start = P.offsets.last_block_id;
1021     }
1022     else
1023     {
1024         P.currentbundle = NULL;
1025     }
1026 
1027     RlistDestroy(P.useargs);
1028     P.useargs = NULL;
1029 }
1030 
ParserHandleQuotedListItem()1031 static inline void ParserHandleQuotedListItem()
1032 {
1033     RlistAppendScalar((Rlist **) &P.currentRlist,
1034                       (void *) P.currentstring);
1035     FREE_AND_NULL(P.currentstring);
1036 }
1037 
1038 #endif // CF3_PARSE_LOGIC_H
1039