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 <mod_files.h>
26 
27 #include <policy.h>
28 #include <syntax.h>
29 
30 static const ConstraintSyntax location_constraints[] =
31 {
32     CONSTRAINT_SYNTAX_GLOBAL,
33     ConstraintSyntaxNewOption("before_after", "before,after", "Menu option, point cursor before of after matched line. Default value: after", SYNTAX_STATUS_NORMAL),
34     ConstraintSyntaxNewOption("first_last", "first,last", "Menu option, choose first or last occurrence of match in file. Default value: last", SYNTAX_STATUS_NORMAL),
35     ConstraintSyntaxNewString("select_line_matching", CF_ANYSTRING, "Regular expression for matching file line location", SYNTAX_STATUS_NORMAL),
36     ConstraintSyntaxNewNull()
37 };
38 
39 static const BodySyntax location_body = BodySyntaxNew("location", location_constraints, NULL, SYNTAX_STATUS_NORMAL);
40 
41 static const ConstraintSyntax edit_field_constraints[] =
42 {
43     CONSTRAINT_SYNTAX_GLOBAL,
44     ConstraintSyntaxNewBool("allow_blank_fields", "true/false allow blank fields in a line (do not purge). Default value: false", SYNTAX_STATUS_NORMAL),
45     ConstraintSyntaxNewBool("extend_fields", "true/false add new fields at end of line if necessary to complete edit. Default value: false", SYNTAX_STATUS_NORMAL),
46     ConstraintSyntaxNewOption("field_operation", "prepend,append,alphanum,delete,set", "Menu option policy for editing subfields. Default value: none", SYNTAX_STATUS_NORMAL),
47     ConstraintSyntaxNewString("field_separator", CF_ANYSTRING, "The regular expression used to separate fields in a line. Default value: none", SYNTAX_STATUS_NORMAL),
48     ConstraintSyntaxNewString("field_value", CF_ANYSTRING, "Set field value to a fixed value", SYNTAX_STATUS_NORMAL),
49     ConstraintSyntaxNewInt("select_field", "0,99999999", "Integer index of the field required 0..n (default starts from 1)", SYNTAX_STATUS_NORMAL),
50     ConstraintSyntaxNewBool("start_fields_from_zero", "If set, the default field numbering starts from 0", SYNTAX_STATUS_NORMAL),
51     ConstraintSyntaxNewString("value_separator", CF_CHARRANGE, "Character separator for subfields inside the selected field. Default value: none", SYNTAX_STATUS_NORMAL),
52     ConstraintSyntaxNewNull()
53 };
54 
55 static const BodySyntax edit_field_body = BodySyntaxNew("edit_field", edit_field_constraints, NULL, SYNTAX_STATUS_NORMAL);
56 
57 static const ConstraintSyntax replace_with_constraints[] =
58 {
59     CONSTRAINT_SYNTAX_GLOBAL,
60     ConstraintSyntaxNewOption("occurrences", "all,first", "Menu option to replace all occurrences or just first (NB the latter is non-convergent). Default value: all", SYNTAX_STATUS_NORMAL),
61     ConstraintSyntaxNewString("replace_value", CF_ANYSTRING, "Value used to replace regular expression matches in search", SYNTAX_STATUS_NORMAL),
62     ConstraintSyntaxNewNull()
63 };
64 
65 static const BodySyntax replace_with_body = BodySyntaxNew("replace_with", replace_with_constraints, NULL, SYNTAX_STATUS_NORMAL);
66 
67 static const ConstraintSyntax select_region_constraints[] =
68 {
69     CONSTRAINT_SYNTAX_GLOBAL,
70     ConstraintSyntaxNewBool("include_start_delimiter", "Whether to include the section delimiter. Default value: false", SYNTAX_STATUS_NORMAL),
71     ConstraintSyntaxNewBool("include_end_delimiter", "Whether to include the section delimiter. Default value: false", SYNTAX_STATUS_NORMAL),
72     ConstraintSyntaxNewString("select_start", CF_ANYSTRING, "Regular expression matching start of edit region", SYNTAX_STATUS_NORMAL),
73     ConstraintSyntaxNewString("select_end", CF_ANYSTRING, "Regular expression matches end of edit region from start", SYNTAX_STATUS_NORMAL),
74     ConstraintSyntaxNewBool("select_end_match_eof", "Whether to include EOF as end of the region. Default value: false", SYNTAX_STATUS_NORMAL),
75     ConstraintSyntaxNewNull()
76 };
77 
78 static const BodySyntax select_region_body = BodySyntaxNew("select_region", select_region_constraints, NULL, SYNTAX_STATUS_NORMAL);
79 
80 static const ConstraintSyntax delete_select_constraints[] =
81 {
82     CONSTRAINT_SYNTAX_GLOBAL,
83     ConstraintSyntaxNewStringList("delete_if_startwith_from_list", CF_ANYSTRING, "Delete line if it starts with a string in the list", SYNTAX_STATUS_NORMAL),
84     ConstraintSyntaxNewStringList("delete_if_not_startwith_from_list", CF_ANYSTRING, "Delete line if it DOES NOT start with a string in the list", SYNTAX_STATUS_NORMAL),
85     ConstraintSyntaxNewStringList("delete_if_match_from_list", CF_ANYSTRING, "Delete line if it fully matches a regex in the list", SYNTAX_STATUS_NORMAL),
86     ConstraintSyntaxNewStringList("delete_if_not_match_from_list", CF_ANYSTRING,"Delete line if it DOES NOT fully match a regex in the list", SYNTAX_STATUS_NORMAL),
87     ConstraintSyntaxNewStringList("delete_if_contains_from_list", CF_ANYSTRING, "Delete line if a regex in the list match a line fragment", SYNTAX_STATUS_NORMAL),
88     ConstraintSyntaxNewStringList("delete_if_not_contains_from_list", CF_ANYSTRING,"Delete line if a regex in the list DOES NOT match a line fragment", SYNTAX_STATUS_NORMAL),
89     ConstraintSyntaxNewNull()
90 };
91 
92 static const BodySyntax delete_select_body = BodySyntaxNew("delete_select", delete_select_constraints, NULL, SYNTAX_STATUS_NORMAL);
93 
94 static const ConstraintSyntax insert_select_constraints[] =
95 {
96     CONSTRAINT_SYNTAX_GLOBAL,
97     ConstraintSyntaxNewStringList("insert_if_startwith_from_list", CF_ANYSTRING, "Insert line if it starts with a string in the list", SYNTAX_STATUS_NORMAL),
98     ConstraintSyntaxNewStringList("insert_if_not_startwith_from_list", CF_ANYSTRING,"Insert line if it DOES NOT start with a string in the list", SYNTAX_STATUS_NORMAL),
99     ConstraintSyntaxNewStringList("insert_if_match_from_list", CF_ANYSTRING, "Insert line if it fully matches a regex in the list", SYNTAX_STATUS_NORMAL),
100     ConstraintSyntaxNewStringList("insert_if_not_match_from_list", CF_ANYSTRING,"Insert line if it DOES NOT fully match a regex in the list", SYNTAX_STATUS_NORMAL),
101     ConstraintSyntaxNewStringList("insert_if_contains_from_list", CF_ANYSTRING,"Insert line if a regex in the list match a line fragment", SYNTAX_STATUS_NORMAL),
102     ConstraintSyntaxNewStringList("insert_if_not_contains_from_list", CF_ANYSTRING, "Insert line if a regex in the list DOES NOT match a line fragment", SYNTAX_STATUS_NORMAL),
103     ConstraintSyntaxNewNull()
104 };
105 
106 static const BodySyntax insert_select_body = BodySyntaxNew("insert_select", insert_select_constraints, NULL, SYNTAX_STATUS_NORMAL);
107 
108 static const ConstraintSyntax CF_INSERTLINES_BODIES[] =
109 {
110     ConstraintSyntaxNewBool("expand_scalars", "Expand any unexpanded variables. Default value: false", SYNTAX_STATUS_NORMAL),
111     ConstraintSyntaxNewOption("insert_type", "literal,string,file,file_preserve_block,preserve_block,preserve_all_lines", "Type of object the promiser string refers to. Default value: literal", SYNTAX_STATUS_NORMAL),
112     ConstraintSyntaxNewBody("insert_select", &insert_select_body, "Insert only if lines pass filter criteria", SYNTAX_STATUS_NORMAL),
113     ConstraintSyntaxNewBody("location", &location_body, "Specify where in a file an insertion will be made", SYNTAX_STATUS_NORMAL),
114     ConstraintSyntaxNewOptionList("whitespace_policy", "ignore_leading,ignore_trailing,ignore_embedded,exact_match", "Criteria for matching and recognizing existing lines", SYNTAX_STATUS_NORMAL),
115     ConstraintSyntaxNewNull()
116 };
117 
118 static const ConstraintSyntax CF_DELETELINES_BODIES[] =
119 {
120     ConstraintSyntaxNewBody("delete_select", &delete_select_body, "Delete only if lines pass filter criteria", SYNTAX_STATUS_NORMAL),
121     ConstraintSyntaxNewBool("not_matching", "true/false negate match criterion. Default value: false", SYNTAX_STATUS_NORMAL),
122     ConstraintSyntaxNewNull()
123 };
124 
125 static const ConstraintSyntax CF_COLUMN_BODIES[] =
126 {
127     ConstraintSyntaxNewBody("edit_field", &edit_field_body, "Edit line-based file as matrix of fields", SYNTAX_STATUS_NORMAL),
128     ConstraintSyntaxNewNull()
129 };
130 
131 static const ConstraintSyntax CF_REPLACE_BODIES[] =
132 {
133     ConstraintSyntaxNewBody("replace_with", &replace_with_body, "Search-replace pattern", SYNTAX_STATUS_NORMAL),
134     ConstraintSyntaxNewNull()
135 };
136 
137 const ConstraintSyntax CF_COMMON_EDITBODIES[] =
138 {
139     ConstraintSyntaxNewBody("select_region", &select_region_body, "Limit edits to a demarked region of the file", SYNTAX_STATUS_NORMAL),
140     ConstraintSyntaxNewNull()
141 };
142 
AclCheck(const Body * body,Seq * errors)143 static bool AclCheck(const Body *body, Seq *errors)
144 {
145     bool success = true;
146 
147     if (BodyHasConstraint(body, "acl_directory_inherit")
148         && BodyHasConstraint(body, "acl_default"))
149     {
150         SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_BODY, body, "An acl body cannot have both acl_directory_inherit and acl_default. Please use acl_default only"));
151         success = false;
152     }
153     if (BodyHasConstraint(body, "specify_inherit_aces")
154         && BodyHasConstraint(body, "specify_default_aces"))
155     {
156         SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_BODY, body, "An acl body cannot have both specify_inherit_aces and specify_default_aces. Please use specify_default_aces only"));
157         success = false;
158     }
159 
160     return success;
161 }
162 
163 static const ConstraintSyntax acl_constraints[] =
164 {
165     CONSTRAINT_SYNTAX_GLOBAL,
166     ConstraintSyntaxNewStringList("aces", "((user|group):[^:]+:[-=+,rwx()dtTabBpcoD]*(:(allow|deny))?)|((all|mask):[-=+,rwx()]*(:(allow|deny))?)", "Native settings for access control entry", SYNTAX_STATUS_NORMAL),
167     ConstraintSyntaxNewOption("acl_directory_inherit", "nochange,parent,specify,clear", "Access control list type for the affected file system", SYNTAX_STATUS_DEPRECATED),
168     ConstraintSyntaxNewOption("acl_default", "nochange,access,specify,clear", "How to apply default (inheritable) access control list", SYNTAX_STATUS_NORMAL),
169     ConstraintSyntaxNewOption("acl_method", "append,overwrite", "Editing method for access control list", SYNTAX_STATUS_NORMAL),
170     ConstraintSyntaxNewOption("acl_type", "generic,posix,ntfs", "Access control list type for the affected file system", SYNTAX_STATUS_NORMAL),
171     ConstraintSyntaxNewStringList("specify_inherit_aces", "((user|group):[^:]+:[-=+,rwx()dtTabBpcoD]*(:(allow|deny))?)|((all|mask):[-=+,rwx()]*(:(allow|deny))?)", "Native settings for access control entry", SYNTAX_STATUS_DEPRECATED),
172     ConstraintSyntaxNewStringList("specify_default_aces", "((user|group):[^:]+:[-=+,rwx()dtTabBpcoD]*(:(allow|deny))?)|((all|mask):[-=+,rwx()]*(:(allow|deny))?)", "Native settings for access control entry", SYNTAX_STATUS_NORMAL),
173     ConstraintSyntaxNewOption("acl_inherit", CF_BOOL ",nochange", "Whether the object inherits its ACL from the parent (Windows only)", SYNTAX_STATUS_NORMAL),
174     ConstraintSyntaxNewNull()
175 };
176 
177 static const BodySyntax acl_body = BodySyntaxNew("acl", acl_constraints, &AclCheck, SYNTAX_STATUS_NORMAL);
178 
179 static const ConstraintSyntax changes_constraints[] =
180 {
181     CONSTRAINT_SYNTAX_GLOBAL,
182     ConstraintSyntaxNewOption("hash", "md5,sha1,sha224,sha256,sha384,sha512,best", "Hash files for change detection", SYNTAX_STATUS_NORMAL),
183     ConstraintSyntaxNewOption("report_changes", "all,stats,content,none", "Specify criteria for change warnings", SYNTAX_STATUS_NORMAL),
184     ConstraintSyntaxNewBool("update_hashes", "Update hash values immediately after change warning", SYNTAX_STATUS_NORMAL),
185     ConstraintSyntaxNewBool("report_diffs","Generate reports summarizing the major differences between individual text files", SYNTAX_STATUS_NORMAL),
186     ConstraintSyntaxNewNull()
187 };
188 
189 static const BodySyntax changes_body = BodySyntaxNew("changes", changes_constraints, NULL, SYNTAX_STATUS_NORMAL);
190 
191 static const ConstraintSyntax depth_search_constraints[] =
192 {
193     CONSTRAINT_SYNTAX_GLOBAL,
194     ConstraintSyntaxNewInt("depth", CF_VALRANGE, "Maximum depth level for search", SYNTAX_STATUS_NORMAL),
195     ConstraintSyntaxNewStringList("exclude_dirs", ".*", "List of regexes of directory names NOT to include in depth search", SYNTAX_STATUS_NORMAL),
196     ConstraintSyntaxNewBool("include_basedir", "true/false include the start/root dir of the search results", SYNTAX_STATUS_NORMAL),
197     ConstraintSyntaxNewStringList("include_dirs", ".*", "List of regexes of directory names to include in depth search", SYNTAX_STATUS_NORMAL),
198     ConstraintSyntaxNewBool("rmdeadlinks", "true/false remove links that point to nowhere. Default value: false", SYNTAX_STATUS_NORMAL),
199     ConstraintSyntaxNewBool("traverse_links", "true/false traverse symbolic links to directories. Default value: false", SYNTAX_STATUS_NORMAL),
200     ConstraintSyntaxNewBool("xdev", "When true files and directories on different devices from the promiser will be excluded from depth_search results. Default value: false", SYNTAX_STATUS_NORMAL),
201     ConstraintSyntaxNewNull()
202 };
203 
204 static const BodySyntax depth_search_body = BodySyntaxNew("depth_search", depth_search_constraints, NULL, SYNTAX_STATUS_NORMAL);
205 
206 static const ConstraintSyntax edit_defaults_constraints[] =
207 {
208     CONSTRAINT_SYNTAX_GLOBAL,
209     ConstraintSyntaxNewOption("edit_backup", "true,false,timestamp,rotate", "Menu option for backup policy on edit changes. Default value: true", SYNTAX_STATUS_NORMAL),
210     ConstraintSyntaxNewBool("empty_file_before_editing", "Baseline memory model of file to zero/empty before commencing promised edits. Default value: false", SYNTAX_STATUS_NORMAL),
211     ConstraintSyntaxNewBool("inherit", "If true this causes the sub-bundle to inherit the private classes of its parent", SYNTAX_STATUS_NORMAL),
212     ConstraintSyntaxNewInt("max_file_size", CF_VALRANGE, "Do not edit files bigger than this number of bytes", SYNTAX_STATUS_NORMAL),
213     ConstraintSyntaxNewBool("recognize_join", "Join together lines that end with a backslash, up to 4kB limit. Default value: false", SYNTAX_STATUS_NORMAL),
214     ConstraintSyntaxNewInt("rotate", "0,99", "How many backups to store if 'rotate' edit_backup strategy is selected. Default value: 1", SYNTAX_STATUS_NORMAL),
215     ConstraintSyntaxNewNull()
216 };
217 
218 static const BodySyntax edit_defaults_body = BodySyntaxNew("edit_defaults", edit_defaults_constraints, NULL, SYNTAX_STATUS_NORMAL);
219 
220 static const ConstraintSyntax delete_constraints[] =
221 {
222     CONSTRAINT_SYNTAX_GLOBAL,
223     ConstraintSyntaxNewOption("dirlinks", "delete,tidy,keep", "Menu option policy for dealing with symbolic links to directories during deletion", SYNTAX_STATUS_NORMAL),
224     ConstraintSyntaxNewBool("rmdirs", "true/false whether to delete empty directories during recursive deletion", SYNTAX_STATUS_NORMAL),
225     ConstraintSyntaxNewNull()
226 };
227 
228 static const BodySyntax delete_body = BodySyntaxNew("delete", delete_constraints, NULL, SYNTAX_STATUS_NORMAL);
229 
230 static const ConstraintSyntax rename_constraints[] =
231 {
232     CONSTRAINT_SYNTAX_GLOBAL,
233     ConstraintSyntaxNewBool("disable", "true/false automatically rename and remove permissions. Default value: false", SYNTAX_STATUS_NORMAL),
234     ConstraintSyntaxNewString("disable_mode", CF_MODERANGE, "The permissions to set when a file is disabled", SYNTAX_STATUS_NORMAL),
235     ConstraintSyntaxNewString("disable_suffix", "", "The suffix to add to files when disabling (.cfdisabled)", SYNTAX_STATUS_NORMAL),
236     ConstraintSyntaxNewString("newname", "", "The desired name for the current file", SYNTAX_STATUS_NORMAL),
237     ConstraintSyntaxNewInt("rotate", "0,99", "Maximum number of file rotations to keep", SYNTAX_STATUS_NORMAL),
238     ConstraintSyntaxNewNull()
239 };
240 
241 static const BodySyntax rename_body = BodySyntaxNew("rename", rename_constraints, NULL, SYNTAX_STATUS_NORMAL);
242 
243 static const ConstraintSyntax perms_constraints[] =
244 {
245     CONSTRAINT_SYNTAX_GLOBAL,
246     ConstraintSyntaxNewStringList("bsdflags", CF_BSDFLAGRANGE, "List of menu options for bsd file system flags to set", SYNTAX_STATUS_NORMAL),
247     ConstraintSyntaxNewStringList("groups", CF_USERRANGE, "List of acceptable groups of group ids, first is change target", SYNTAX_STATUS_NORMAL),
248     ConstraintSyntaxNewString("mode", CF_MODERANGE, "File permissions (like posix chmod)", SYNTAX_STATUS_NORMAL),
249     ConstraintSyntaxNewStringList("owners", CF_USERRANGE, "List of acceptable owners or user ids, first is change target", SYNTAX_STATUS_NORMAL),
250     ConstraintSyntaxNewBool("rxdirs", "true/false add execute flag for directories if read flag is set", SYNTAX_STATUS_NORMAL),
251     ConstraintSyntaxNewNull()
252 };
253 
254 static const BodySyntax perms_body = BodySyntaxNew("perms", perms_constraints, NULL, SYNTAX_STATUS_NORMAL);
255 
256 static const ConstraintSyntax file_select_constraints[] =
257 {
258     CONSTRAINT_SYNTAX_GLOBAL,
259     ConstraintSyntaxNewStringList("leaf_name", "", "List of regexes that match an acceptable name", SYNTAX_STATUS_NORMAL),
260     ConstraintSyntaxNewStringList("path_name", CF_ABSPATHRANGE, "List of pathnames to match acceptable target", SYNTAX_STATUS_NORMAL),
261     ConstraintSyntaxNewStringList("search_mode", CF_MODERANGE, "A list of mode masks for acceptable file permissions", SYNTAX_STATUS_NORMAL),
262     ConstraintSyntaxNewIntRange("search_size", "0,inf", "Integer range of file sizes", SYNTAX_STATUS_NORMAL),
263     ConstraintSyntaxNewStringList("search_owners", "", "List of acceptable user names or ids for the file, or regexes to match", SYNTAX_STATUS_NORMAL),
264     ConstraintSyntaxNewStringList("search_groups", "", "List of acceptable group names or ids for the file, or regexes to match", SYNTAX_STATUS_NORMAL),
265     ConstraintSyntaxNewStringList("search_bsdflags", CF_BSDFLAGRANGE, "String of flags for bsd file system flags expected set", SYNTAX_STATUS_NORMAL),
266     ConstraintSyntaxNewIntRange("ctime", CF_TIMERANGE, "Range of change times (ctime) for acceptable files", SYNTAX_STATUS_NORMAL),
267     ConstraintSyntaxNewIntRange("mtime", CF_TIMERANGE, "Range of modification times (mtime) for acceptable files", SYNTAX_STATUS_NORMAL),
268     ConstraintSyntaxNewIntRange("atime", CF_TIMERANGE, "Range of access times (atime) for acceptable files", SYNTAX_STATUS_NORMAL),
269     ConstraintSyntaxNewString("exec_regex", CF_ANYSTRING, "Matches file if this regular expression matches any full line returned by the command", SYNTAX_STATUS_NORMAL),
270     ConstraintSyntaxNewString("exec_program", CF_ABSPATHRANGE, "Execute this command on each file and match if the exit status is zero", SYNTAX_STATUS_NORMAL),
271     ConstraintSyntaxNewOptionList("file_types", "plain,reg,symlink,dir,socket,fifo,door,char,block", "List of acceptable file types from menu choices", SYNTAX_STATUS_NORMAL),
272     ConstraintSyntaxNewStringList("issymlinkto", "", "List of regular expressions to match file objects", SYNTAX_STATUS_NORMAL),
273     ConstraintSyntaxNewString("file_result", "[!*(leaf_name|path_name|file_types|mode|size|owner|group|atime|ctime|mtime|issymlinkto|exec_regex|exec_program|bsdflags)[|&.]*]*",
274                         "Logical expression combining classes defined by file search criteria", SYNTAX_STATUS_NORMAL),
275     ConstraintSyntaxNewNull()
276 };
277 
278 static const BodySyntax file_select_body = BodySyntaxNew("file_select", file_select_constraints, NULL, SYNTAX_STATUS_NORMAL);
279 
280 /* Copy and link are really the same body and should have
281    non-overlapping patterns so that they are XOR but it's
282    okay that some names overlap (like source) as there is
283    no ambiguity in XOR */
284 
285 static const ConstraintSyntax link_from_constraints[] =
286 {
287     CONSTRAINT_SYNTAX_GLOBAL,
288     ConstraintSyntaxNewStringList("copy_patterns", "", "A set of patterns that should be copied and synchronized instead of linked", SYNTAX_STATUS_NORMAL),
289     ConstraintSyntaxNewBool("link_children", "true/false whether to link all directory's children to source originals. Default value: false", SYNTAX_STATUS_NORMAL),
290     ConstraintSyntaxNewOption("link_type", CF_LINKRANGE, "The type of link used to alias the file. Default value: symlink", SYNTAX_STATUS_NORMAL),
291     ConstraintSyntaxNewString("source", CF_PATHRANGE, "The source file to which the link should point", SYNTAX_STATUS_NORMAL),
292     ConstraintSyntaxNewOption("when_linking_children", "override_file,if_no_such_file", "Policy for overriding existing files when linking directories of children", SYNTAX_STATUS_NORMAL),
293     ConstraintSyntaxNewOption("when_no_source", "force,delete,nop", "Behaviour when the source file to link to does not exist. Default value: nop", SYNTAX_STATUS_NORMAL),
294     ConstraintSyntaxNewNull()
295 };
296 
297 static const BodySyntax link_from_body = BodySyntaxNew("link_from", link_from_constraints, NULL, SYNTAX_STATUS_NORMAL);
298 
299 static const ConstraintSyntax copy_from_constraints[] =
300 {
301     /* We use CF_PATHRANGE due to collision with LINKTO_BODY and a bug lurking in
302      * a verification stage -- this attribute gets picked instead of another
303      * 'source'
304      */
305     CONSTRAINT_SYNTAX_GLOBAL,
306     ConstraintSyntaxNewString("source", CF_PATHRANGE, "Reference source file from which to copy", SYNTAX_STATUS_NORMAL),
307     ConstraintSyntaxNewStringList("servers", "[A-Za-z0-9_.:\\-\\[\\]]+", "List of servers in order of preference from which to copy", SYNTAX_STATUS_NORMAL),
308     ConstraintSyntaxNewBool("collapse_destination_dir", "Copy files from subdirectories to the root destination directory. Default: false", SYNTAX_STATUS_NORMAL),
309     ConstraintSyntaxNewOption("compare", "atime,mtime,ctime,digest,hash,exists,binary", "Menu option policy for comparing source and image file attributes. Default: mtime or ctime differs", SYNTAX_STATUS_NORMAL),
310     ConstraintSyntaxNewOption("copy_backup", "true,false,timestamp", "Menu option policy for file backup/version control. Default value: true", SYNTAX_STATUS_NORMAL),
311     ConstraintSyntaxNewBool("encrypt", "true/false use encrypted data stream to connect to remote host. Default value: false", SYNTAX_STATUS_NORMAL),
312     ConstraintSyntaxNewBool("check_root", "true/false check permissions on the root directory when depth_search", SYNTAX_STATUS_NORMAL),
313     ConstraintSyntaxNewStringList("copylink_patterns", "", "List of patterns matching files that should be copied instead of linked", SYNTAX_STATUS_NORMAL),
314     ConstraintSyntaxNewIntRange("copy_size", "0,inf", "Integer range of file sizes that may be copied", SYNTAX_STATUS_NORMAL),
315     ConstraintSyntaxNewOption("findertype", "MacOSX", "Menu option for default finder type on MacOSX", SYNTAX_STATUS_NORMAL),
316     ConstraintSyntaxNewStringList("linkcopy_patterns", "", "List of patterns matching files that should be replaced with symbolic links", SYNTAX_STATUS_NORMAL),
317     ConstraintSyntaxNewOption("link_type", CF_LINKRANGE, "Menu option for type of links to use when copying. Default value: symlink", SYNTAX_STATUS_NORMAL),
318     ConstraintSyntaxNewBool("force_update", "true/false force copy update always. Default value: false", SYNTAX_STATUS_NORMAL),
319     ConstraintSyntaxNewBool("force_ipv4", "true/false force use of ipv4 on ipv6 enabled network. Default value: false", SYNTAX_STATUS_NORMAL),
320     ConstraintSyntaxNewString("portnumber", "", "Port number or service name to connect to on server host", SYNTAX_STATUS_NORMAL),
321     ConstraintSyntaxNewBool("preserve", "true/false whether to preserve file permissions on copied file. Default value: false", SYNTAX_STATUS_NORMAL),
322     ConstraintSyntaxNewBool("purge", "true/false purge files on client that do not match files on server when a depth_search is used. Default value: false", SYNTAX_STATUS_NORMAL),
323     ConstraintSyntaxNewBool("stealth", "true/false whether to preserve time stamps on copied file. Default value: false", SYNTAX_STATUS_NORMAL),
324     ConstraintSyntaxNewInt("timeout", "1,3600", "Connection timeout, seconds", SYNTAX_STATUS_NORMAL),
325     ConstraintSyntaxNewBool("trustkey", "true/false trust public keys from remote server if previously unknown. Default value: false", SYNTAX_STATUS_NORMAL),
326     ConstraintSyntaxNewBool("type_check", "true/false compare file types before copying and require match", SYNTAX_STATUS_NORMAL),
327     ConstraintSyntaxNewBool("verify", "true/false verify transferred file by hashing after copy (resource penalty). Default value: false", SYNTAX_STATUS_NORMAL),
328     ConstraintSyntaxNewOption("protocol_version", "0,undefined,1,classic,2,latest", "CFEngine protocol version to use when connecting to the server. Default: undefined", SYNTAX_STATUS_NORMAL),
329     ConstraintSyntaxNewBool("missing_ok", "true/false Do not treat missing file as an error. Default value: false", SYNTAX_STATUS_NORMAL),
330     ConstraintSyntaxNewNull()
331 };
332 
333 static const BodySyntax copy_from_body = BodySyntaxNew("copy_from", copy_from_constraints, NULL, SYNTAX_STATUS_NORMAL);
334 
335 static const ConstraintSyntax CF_FILES_BODIES[] =
336 {
337     ConstraintSyntaxNewBody("acl", &acl_body, "Criteria for access control lists on file", SYNTAX_STATUS_NORMAL),
338     ConstraintSyntaxNewBody("changes", &changes_body, "Criteria for change management", SYNTAX_STATUS_NORMAL),
339     ConstraintSyntaxNewBody("copy_from", &copy_from_body, "Criteria for copying file from a source", SYNTAX_STATUS_NORMAL),
340     ConstraintSyntaxNewBool("create", "true/false whether to create non-existing file. Default value: false", SYNTAX_STATUS_NORMAL),
341     ConstraintSyntaxNewBody("delete", &delete_body, "Criteria for deleting files", SYNTAX_STATUS_NORMAL),
342     ConstraintSyntaxNewString("content", CF_ANYSTRING, "Complete content the promised file should contain", SYNTAX_STATUS_NORMAL),
343     ConstraintSyntaxNewBody("depth_search", &depth_search_body, "Criteria for file depth searches", SYNTAX_STATUS_NORMAL),
344     ConstraintSyntaxNewBody("edit_defaults", &edit_defaults_body, "Default promise details for file edits", SYNTAX_STATUS_NORMAL),
345     ConstraintSyntaxNewBundle("edit_line", "Line editing model for file", SYNTAX_STATUS_NORMAL),
346     ConstraintSyntaxNewString("edit_template", CF_ABSPATHRANGE, "The name of a special CFEngine template file to expand", SYNTAX_STATUS_NORMAL),
347     ConstraintSyntaxNewString("edit_template_string", CF_ANYSTRING, "Template string to expand", SYNTAX_STATUS_NORMAL),
348     ConstraintSyntaxNewBundle("edit_xml", "XML editing model for file", SYNTAX_STATUS_NORMAL),
349     ConstraintSyntaxNewBody("file_select", &file_select_body, "Choose which files select in a search", SYNTAX_STATUS_NORMAL),
350     ConstraintSyntaxNewOption("file_type", "regular,fifo", "Type of file to create. Default value: regular", SYNTAX_STATUS_NORMAL),
351     ConstraintSyntaxNewBody("link_from", &link_from_body, "Criteria for linking file from a source", SYNTAX_STATUS_NORMAL),
352     ConstraintSyntaxNewBool("move_obstructions", "true/false whether to move obstructions to file-object creation. Default value: false", SYNTAX_STATUS_NORMAL),
353     ConstraintSyntaxNewOption("pathtype", "literal,regex,guess", "Menu option for interpreting promiser file object", SYNTAX_STATUS_NORMAL),
354     ConstraintSyntaxNewBody("perms", &perms_body, "Criteria for setting permissions on a file", SYNTAX_STATUS_NORMAL),
355     ConstraintSyntaxNewBody("rename", &rename_body, "Criteria for renaming files", SYNTAX_STATUS_NORMAL),
356     ConstraintSyntaxNewString("repository", CF_ABSPATHRANGE, "Name of a repository for versioning", SYNTAX_STATUS_NORMAL),
357     ConstraintSyntaxNewBool("touch", "true/false whether to touch time stamps on file", SYNTAX_STATUS_NORMAL),
358     ConstraintSyntaxNewString("transformer", CF_ABSPATHRANGE, "Command (with full path) used to transform current file (no shell wrapper used)", SYNTAX_STATUS_NORMAL),
359     ConstraintSyntaxNewOption("template_method", "cfengine,inline_mustache,mustache", "", SYNTAX_STATUS_NORMAL),
360     ConstraintSyntaxNewContainer("template_data", "", SYNTAX_STATUS_NORMAL),
361 
362     ConstraintSyntaxNewNull()
363 };
364 
365 // edit_xml body syntax
366 const ConstraintSyntax CF_COMMON_XMLBODIES[] =
367 {
368     ConstraintSyntaxNewString("build_xpath", "", "Build an XPath within the XML file", SYNTAX_STATUS_NORMAL),
369     ConstraintSyntaxNewString("select_xpath", "", "Select the XPath node in the XML file to edit", SYNTAX_STATUS_NORMAL),
370     ConstraintSyntaxNewNull()
371 };
372 
373 static const ConstraintSyntax CF_INSERTTAGS_BODIES[] =
374 {
375     ConstraintSyntaxNewNull()
376 };
377 
378 static const ConstraintSyntax CF_DELETETAGS_BODIES[] =
379 {
380     ConstraintSyntaxNewNull()
381 };
382 
383 static const ConstraintSyntax CF_INSERTATTRIBUTES_BODIES[] =
384 {
385     ConstraintSyntaxNewString("attribute_value", "", "Value of the attribute to be inserted into the XPath node of the XML file", SYNTAX_STATUS_NORMAL),
386     ConstraintSyntaxNewNull()
387 };
388 
389 static const ConstraintSyntax CF_DELETEATTRIBUTES_BODIES[] =
390 {
391     ConstraintSyntaxNewNull()
392 };
393 
394 
395 // Master Syntax for Files
396 
397 const PromiseTypeSyntax CF_FILES_PROMISE_TYPES[] =
398 {
399     /* Body lists belonging to "files:" type in Agent */
400 
401     PromiseTypeSyntaxNew("agent", "files", CF_FILES_BODIES, NULL, SYNTAX_STATUS_NORMAL),
402 
403     /* Body lists belonging to th edit_line sub-bundle of files: */
404 
405     PromiseTypeSyntaxNew("edit_line", "*", CF_COMMON_EDITBODIES, NULL, SYNTAX_STATUS_NORMAL),
406     PromiseTypeSyntaxNew("edit_line", "delete_lines", CF_DELETELINES_BODIES, NULL, SYNTAX_STATUS_NORMAL),
407     PromiseTypeSyntaxNew("edit_line", "insert_lines", CF_INSERTLINES_BODIES, NULL, SYNTAX_STATUS_NORMAL),
408     PromiseTypeSyntaxNew("edit_line", "field_edits", CF_COLUMN_BODIES, NULL, SYNTAX_STATUS_NORMAL),
409     PromiseTypeSyntaxNew("edit_line", "replace_patterns", CF_REPLACE_BODIES, NULL, SYNTAX_STATUS_NORMAL),
410 
411     PromiseTypeSyntaxNew("edit_xml", "*", CF_COMMON_XMLBODIES, NULL, SYNTAX_STATUS_NORMAL),
412     PromiseTypeSyntaxNew("edit_xml", "build_xpath", CF_INSERTTAGS_BODIES, NULL, SYNTAX_STATUS_NORMAL),
413     PromiseTypeSyntaxNew("edit_xml", "delete_tree", CF_DELETETAGS_BODIES, NULL, SYNTAX_STATUS_NORMAL),
414     PromiseTypeSyntaxNew("edit_xml", "insert_tree", CF_INSERTTAGS_BODIES, NULL, SYNTAX_STATUS_NORMAL),
415     PromiseTypeSyntaxNew("edit_xml", "delete_attribute", CF_DELETEATTRIBUTES_BODIES, NULL, SYNTAX_STATUS_NORMAL),
416     PromiseTypeSyntaxNew("edit_xml", "set_attribute", CF_INSERTATTRIBUTES_BODIES, NULL, SYNTAX_STATUS_NORMAL),
417     PromiseTypeSyntaxNew("edit_xml", "delete_text", CF_DELETETAGS_BODIES, NULL, SYNTAX_STATUS_NORMAL),
418     PromiseTypeSyntaxNew("edit_xml", "set_text", CF_INSERTTAGS_BODIES, NULL, SYNTAX_STATUS_NORMAL),
419     PromiseTypeSyntaxNew("edit_xml", "insert_text", CF_INSERTTAGS_BODIES, NULL, SYNTAX_STATUS_NORMAL),
420 
421     PromiseTypeSyntaxNewNull()
422 };
423