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 <files_select.h>
26
27 #include <eval_context.h>
28 #include <files_names.h>
29 #include <files_interfaces.h>
30 #include <promises.h>
31 #include <match_scope.h>
32 #include <string_lib.h>
33 #include <files_lib.h>
34 #include <pipes.h>
35 #include <promises.h>
36 #include <exec_tools.h>
37 #include <chflags.h>
38
39 static bool SelectTypeMatch(const struct stat *lstatptr, Rlist *crit);
40 static bool SelectOwnerMatch(EvalContext *ctx, char *path, const struct stat *lstatptr, Rlist *crit);
41 static bool SelectModeMatch(const struct stat *lstatptr, Rlist *ls);
42 static bool SelectTimeMatch(time_t stattime, time_t fromtime, time_t totime);
43 static bool SelectNameRegexMatch(EvalContext *ctx, const char *filename, char *crit);
44 static bool SelectPathRegexMatch(EvalContext *ctx, char *filename, char *crit);
45 static bool SelectExecRegexMatch(EvalContext *ctx, char *filename, char *crit, char *prog);
46 static bool SelectIsSymLinkTo(EvalContext *ctx, char *filename, Rlist *crit);
47 static bool SelectExecProgram(char *filename, char *command);
48 static bool SelectSizeMatch(size_t size, size_t min, size_t max);
49
50 #if !defined(__MINGW32__)
51 static bool SelectGroupMatch(EvalContext *ctx, const struct stat *lstatptr, Rlist *crit);
52 #endif
53
54 #if defined HAVE_CHFLAGS
55 static bool SelectBSDMatch(const struct stat *lstatptr, Rlist *bsdflags);
56 #endif
57
SelectLeaf(EvalContext * ctx,char * path,const struct stat * sb,const FileSelect * fs)58 bool SelectLeaf(EvalContext *ctx, char *path, const struct stat *sb, const FileSelect *fs)
59 {
60 Rlist *rp;
61
62 StringSet *leaf_attr = StringSetNew();
63
64 #ifdef __MINGW32__
65 if (fs->issymlinkto != NULL)
66 {
67 Log(LOG_LEVEL_VERBOSE,
68 "files_select.issymlinkto is ignored on Windows (symbolic links are not supported by Windows)");
69 }
70
71 if (fs->groups != NULL)
72 {
73 Log(LOG_LEVEL_VERBOSE,
74 "files_select.search_groups is ignored on Windows (file groups are not supported by Windows)");
75 }
76
77 if (fs->bsdflags != NULL)
78 {
79 Log(LOG_LEVEL_VERBOSE, "files_select.search_bsdflags is ignored on Windows");
80 }
81 #endif /* __MINGW32__ */
82
83 if (fs->name == NULL)
84 {
85 StringSetAdd(leaf_attr, xstrdup("leaf_name"));
86 }
87
88 for (rp = fs->name; rp != NULL; rp = rp->next)
89 {
90 if (SelectNameRegexMatch(ctx, path, RlistScalarValue(rp)))
91 {
92 StringSetAdd(leaf_attr, xstrdup("leaf_name"));
93 break;
94 }
95 }
96
97 if (fs->path == NULL)
98 {
99 StringSetAdd(leaf_attr, xstrdup("leaf_path"));
100 }
101
102 for (rp = fs->path; rp != NULL; rp = rp->next)
103 {
104 if (SelectPathRegexMatch(ctx, path, RlistScalarValue(rp)))
105 {
106 StringSetAdd(leaf_attr, xstrdup("path_name"));
107 break;
108 }
109 }
110
111 if (SelectTypeMatch(sb, fs->filetypes))
112 {
113 StringSetAdd(leaf_attr, xstrdup("file_types"));
114 }
115
116 if ((fs->owners) && (SelectOwnerMatch(ctx, path, sb, fs->owners)))
117 {
118 StringSetAdd(leaf_attr, xstrdup("owner"));
119 }
120
121 if (fs->owners == NULL)
122 {
123 StringSetAdd(leaf_attr, xstrdup("owner"));
124 }
125
126 #ifdef __MINGW32__
127 StringSetAdd(leaf_attr, xstrdup("group"));
128
129 #else /* !__MINGW32__ */
130 if ((fs->groups) && (SelectGroupMatch(ctx, sb, fs->groups)))
131 {
132 StringSetAdd(leaf_attr, xstrdup("group"));
133 }
134
135 if (fs->groups == NULL)
136 {
137 StringSetAdd(leaf_attr, xstrdup("group"));
138 }
139 #endif /* !__MINGW32__ */
140
141 if (SelectModeMatch(sb, fs->perms))
142 {
143 StringSetAdd(leaf_attr, xstrdup("mode"));
144 }
145
146 #if defined HAVE_CHFLAGS
147 if (SelectBSDMatch(sb, fs->bsdflags))
148 {
149 StringSetAdd(leaf_attr, xstrdup("bsdflags"));
150 }
151 #endif
152
153 if (SelectTimeMatch(sb->st_atime, fs->min_atime, fs->max_atime))
154 {
155 StringSetAdd(leaf_attr, xstrdup("atime"));
156 }
157
158 if (SelectTimeMatch(sb->st_ctime, fs->min_ctime, fs->max_ctime))
159 {
160 StringSetAdd(leaf_attr, xstrdup("ctime"));
161 }
162
163 if (SelectSizeMatch(sb->st_size, fs->min_size, fs->max_size))
164 {
165 StringSetAdd(leaf_attr, xstrdup("size"));
166 }
167
168 if (SelectTimeMatch(sb->st_mtime, fs->min_mtime, fs->max_mtime))
169 {
170 StringSetAdd(leaf_attr, xstrdup("mtime"));
171 }
172
173 if ((fs->issymlinkto) && (SelectIsSymLinkTo(ctx, path, fs->issymlinkto)))
174 {
175 StringSetAdd(leaf_attr, xstrdup("issymlinkto"));
176 }
177
178 if ((fs->exec_regex) && (SelectExecRegexMatch(ctx, path, fs->exec_regex, fs->exec_program)))
179 {
180 StringSetAdd(leaf_attr, xstrdup("exec_regex"));
181 }
182
183 if ((fs->exec_program) && (SelectExecProgram(path, fs->exec_program)))
184 {
185 StringSetAdd(leaf_attr, xstrdup("exec_program"));
186 }
187
188 bool result = EvalFileResult(fs->result, leaf_attr);
189
190 Log(LOG_LEVEL_VERBOSE, "file_select result '%s' on '%s' was '%s'",
191 fs->result, path,
192 result ? "true" : "false");
193
194 StringSetDestroy(leaf_attr);
195
196 return result;
197 }
198
199 /*******************************************************************/
200 /* Level */
201 /*******************************************************************/
202
SelectSizeMatch(size_t size,size_t min,size_t max)203 static bool SelectSizeMatch(size_t size, size_t min, size_t max)
204 {
205 if ((size <= max) && (size >= min))
206 {
207 return true;
208 }
209
210 return false;
211 }
212
213 /*******************************************************************/
214
SelectTypeMatch(const struct stat * lstatptr,Rlist * crit)215 static bool SelectTypeMatch(const struct stat *lstatptr, Rlist *crit)
216 {
217 Rlist *rp;
218
219 StringSet *leafattrib = StringSetNew();
220
221 if (S_ISREG(lstatptr->st_mode))
222 {
223 StringSetAdd(leafattrib, xstrdup("reg"));
224 StringSetAdd(leafattrib, xstrdup("plain"));
225 }
226
227 if (S_ISDIR(lstatptr->st_mode))
228 {
229 StringSetAdd(leafattrib, xstrdup("dir"));
230 }
231
232 #ifndef __MINGW32__
233 if (S_ISLNK(lstatptr->st_mode))
234 {
235 StringSetAdd(leafattrib, xstrdup("symlink"));
236 }
237
238 if (S_ISFIFO(lstatptr->st_mode))
239 {
240 StringSetAdd(leafattrib, xstrdup("fifo"));
241 }
242
243 if (S_ISSOCK(lstatptr->st_mode))
244 {
245 StringSetAdd(leafattrib, xstrdup("socket"));
246 }
247
248 if (S_ISCHR(lstatptr->st_mode))
249 {
250 StringSetAdd(leafattrib, xstrdup("char"));
251 }
252
253 if (S_ISBLK(lstatptr->st_mode))
254 {
255 StringSetAdd(leafattrib, xstrdup("block"));
256 }
257 #endif /* !__MINGW32__ */
258
259 #ifdef HAVE_DOOR_CREATE
260 if (S_ISDOOR(lstatptr->st_mode))
261 {
262 StringSetAdd(leafattrib, xstrdup("door"));
263 }
264 #endif
265
266 for (rp = crit; rp != NULL; rp = rp->next)
267 {
268 if (EvalFileResult(RlistScalarValue(rp), leafattrib))
269 {
270 StringSetDestroy(leafattrib);
271 return true;
272 }
273 }
274
275 StringSetDestroy(leafattrib);
276 return false;
277 }
278
SelectOwnerMatch(EvalContext * ctx,char * path,const struct stat * lstatptr,Rlist * crit)279 static bool SelectOwnerMatch(EvalContext *ctx, char *path, const struct stat *lstatptr, Rlist *crit)
280 {
281 Rlist *rp;
282 char ownerName[CF_BUFSIZE];
283
284 StringSet *leafattrib = StringSetNew();
285
286 #ifndef __MINGW32__ // no uids on Windows
287 char buffer[CF_SMALLBUF];
288 snprintf(buffer, CF_SMALLBUF, "%ju", (uintmax_t) lstatptr->st_uid);
289 StringSetAdd(leafattrib, xstrdup(buffer));
290 #endif /* __MINGW32__ */
291
292 bool gotOwner = GetOwnerName(path, lstatptr, ownerName, sizeof(ownerName));
293
294 if (gotOwner)
295 {
296 StringSetAdd(leafattrib, xstrdup(ownerName));
297 }
298 else
299 {
300 StringSetAdd(leafattrib, xstrdup("none"));
301 }
302
303 for (rp = crit; rp != NULL; rp = rp->next)
304 {
305 if (EvalFileResult(RlistScalarValue(rp), leafattrib))
306 {
307 Log(LOG_LEVEL_DEBUG, "Select owner match");
308 StringSetDestroy(leafattrib);
309 return true;
310 }
311
312 if (gotOwner && (FullTextMatch(ctx, RlistScalarValue(rp), ownerName)))
313 {
314 Log(LOG_LEVEL_DEBUG, "Select owner match");
315 StringSetDestroy(leafattrib);
316 return true;
317 }
318
319 #ifndef __MINGW32__
320 if (FullTextMatch(ctx, RlistScalarValue(rp), buffer))
321 {
322 Log(LOG_LEVEL_DEBUG, "Select owner match");
323 StringSetDestroy(leafattrib);
324 return true;
325 }
326 #endif /* !__MINGW32__ */
327 }
328
329 StringSetDestroy(leafattrib);
330 return false;
331 }
332
333 /*******************************************************************/
334
SelectModeMatch(const struct stat * lstatptr,Rlist * list)335 static bool SelectModeMatch(const struct stat *lstatptr, Rlist *list)
336 {
337 mode_t newperm, plus, minus;
338 Rlist *rp;
339
340 for (rp = list; rp != NULL; rp = rp->next)
341 {
342 plus = 0;
343 minus = 0;
344
345 if (!ParseModeString(RlistScalarValue(rp), &plus, &minus))
346 {
347 Log(LOG_LEVEL_ERR, "Problem validating a mode string '%s' in search filter", RlistScalarValue(rp));
348 continue;
349 }
350
351 newperm = (lstatptr->st_mode & 07777);
352 newperm |= plus;
353 newperm &= ~minus;
354
355 if ((newperm & 07777) == (lstatptr->st_mode & 07777))
356 {
357 return true;
358 }
359 }
360
361 return false;
362 }
363
364 /*******************************************************************/
365
366 #if defined HAVE_CHFLAGS
SelectBSDMatch(const struct stat * lstatptr,Rlist * bsdflags)367 static bool SelectBSDMatch(const struct stat *lstatptr, Rlist *bsdflags)
368 {
369 u_long newflags, plus, minus;
370
371 if (!ParseFlagString(bsdflags, &plus, &minus))
372 {
373 Log(LOG_LEVEL_ERR, "Problem validating a BSD flag string");
374 }
375
376 newflags = (lstatptr->st_flags & CHFLAGS_MASK);
377 newflags |= plus;
378 newflags &= ~minus;
379
380 if ((newflags & CHFLAGS_MASK) == (lstatptr->st_flags & CHFLAGS_MASK)) /* file okay */
381 {
382 return true;
383 }
384
385 return false;
386 }
387 #endif
388 /*******************************************************************/
389
SelectTimeMatch(time_t stattime,time_t fromtime,time_t totime)390 static bool SelectTimeMatch(time_t stattime, time_t fromtime, time_t totime)
391 {
392 return ((fromtime < stattime) && (stattime < totime));
393 }
394
395 /*******************************************************************/
396
SelectNameRegexMatch(EvalContext * ctx,const char * filename,char * crit)397 static bool SelectNameRegexMatch(EvalContext *ctx, const char *filename, char *crit)
398 {
399 if (FullTextMatch(ctx, crit, ReadLastNode(filename)))
400 {
401 return true;
402 }
403
404 return false;
405 }
406
407 /*******************************************************************/
408
SelectPathRegexMatch(EvalContext * ctx,char * filename,char * crit)409 static bool SelectPathRegexMatch(EvalContext *ctx, char *filename, char *crit)
410 {
411 if (FullTextMatch(ctx, crit, filename))
412 {
413 return true;
414 }
415
416 return false;
417 }
418
419 /*******************************************************************/
420
SelectExecRegexMatch(EvalContext * ctx,char * filename,char * crit,char * prog)421 static bool SelectExecRegexMatch(EvalContext *ctx, char *filename, char *crit, char *prog)
422 {
423 // insert real value of $(this.promiser) in command
424
425 char *buf_tmp = SearchAndReplace(prog, "$(this.promiser)", filename);
426 char *buf = SearchAndReplace(buf_tmp, "${this.promiser}", filename);
427 free(buf_tmp);
428
429 FILE *pp = cf_popen(buf, "r", true);
430 if (pp == NULL)
431 {
432 Log(LOG_LEVEL_ERR, "Couldn't open pipe to command '%s'. (cf_popen: %s)", buf, GetErrorStr());
433 free(buf);
434 return false;
435 }
436
437 size_t line_size = CF_BUFSIZE;
438 char *line = xmalloc(line_size);
439
440 for (;;)
441 {
442 ssize_t res = CfReadLine(&line, &line_size, pp);
443 if (res == -1)
444 {
445 if (!feof(pp))
446 {
447 Log(LOG_LEVEL_ERR, "Error reading output from command '%s'. (fgets: %s)", buf, GetErrorStr());
448 }
449 cf_pclose(pp);
450 free(line);
451 free(buf);
452 return false;
453 }
454
455 if (FullTextMatch(ctx, crit, line))
456 {
457 cf_pclose(pp);
458 free(line);
459 free(buf);
460 return true;
461 }
462 }
463
464 cf_pclose(pp);
465 free(line);
466 free(buf);
467 return false;
468 }
469
470 /*******************************************************************/
471
SelectIsSymLinkTo(EvalContext * ctx,char * filename,Rlist * crit)472 static bool SelectIsSymLinkTo(EvalContext *ctx, char *filename, Rlist *crit)
473 {
474 #ifndef __MINGW32__
475 char buffer[CF_BUFSIZE];
476 Rlist *rp;
477
478 for (rp = crit; rp != NULL; rp = rp->next)
479 {
480 memset(buffer, 0, CF_BUFSIZE);
481
482 struct stat statbuf;
483
484 // Don't worry if this gives an error, that's handled above us.
485
486 // We're calling lstat() here to avoid dereferencing the
487 // symlink... and we only care if the inode is a directory.
488 if (lstat(filename, &statbuf) == -1)
489 {
490 // Do nothing.
491 }
492 else if (!S_ISLNK(statbuf.st_mode))
493 {
494 Log(LOG_LEVEL_DEBUG, "Skipping readlink() on non-symlink %s", filename);
495 return false;
496 }
497
498 if (readlink(filename, buffer, CF_BUFSIZE - 1) == -1)
499 {
500 Log(LOG_LEVEL_ERR, "Unable to read link '%s' in filter. (readlink: %s)",
501 filename, GetErrorStr());
502 return false;
503 }
504
505 if (FullTextMatch(ctx, RlistScalarValue(rp), buffer))
506 {
507 return true;
508 }
509 }
510 #endif /* !__MINGW32__ */
511 return false;
512 }
513
514 /*******************************************************************/
515
SelectExecProgram(char * filename,char * command)516 static bool SelectExecProgram(char *filename, char *command)
517 /* command can include $(this.promiser) for the name of the file */
518 {
519 // insert real value of $(this.promiser) in command
520
521 char *buf_tmp = SearchAndReplace(command, "$(this.promiser)", filename);
522 char *buf = SearchAndReplace(buf_tmp, "${this.promiser}", filename);
523 free(buf_tmp);
524
525 bool returns_zero = ShellCommandReturnsZero(buf, SHELL_TYPE_NONE);
526
527 if (returns_zero)
528 {
529 Log(LOG_LEVEL_DEBUG, "Select ExecProgram match for '%s'", buf);
530 free(buf);
531 return true;
532 }
533 else
534 {
535 free(buf);
536 return false;
537 }
538 }
539
540 #ifndef __MINGW32__
541
542 /*******************************************************************/
543 /* Unix implementations */
544 /*******************************************************************/
545
GetOwnerName(ARG_UNUSED char * path,const struct stat * lstatptr,char * owner,int ownerSz)546 bool GetOwnerName(ARG_UNUSED char *path, const struct stat *lstatptr, char *owner, int ownerSz)
547 {
548 struct passwd *pw;
549
550 memset(owner, 0, ownerSz);
551 pw = getpwuid(lstatptr->st_uid);
552
553 if (pw == NULL)
554 {
555 Log(LOG_LEVEL_ERR, "Could not get owner name of user with 'uid=%ju'. (getpwuid: %s)",
556 (uintmax_t)lstatptr->st_uid, GetErrorStr());
557 return false;
558 }
559
560 strncpy(owner, pw->pw_name, ownerSz - 1);
561
562 return true;
563 }
564
565 /*******************************************************************/
566
SelectGroupMatch(EvalContext * ctx,const struct stat * lstatptr,Rlist * crit)567 static bool SelectGroupMatch(EvalContext *ctx, const struct stat *lstatptr, Rlist *crit)
568 {
569 char buffer[CF_SMALLBUF];
570 struct group *gr;
571 Rlist *rp;
572
573 StringSet *leafattrib = StringSetNew();
574
575 snprintf(buffer, CF_SMALLBUF, "%ju", (uintmax_t) lstatptr->st_gid);
576 StringSetAdd(leafattrib, xstrdup(buffer));
577
578 if ((gr = getgrgid(lstatptr->st_gid)) != NULL)
579 {
580 StringSetAdd(leafattrib, xstrdup(gr->gr_name));
581 }
582 else
583 {
584 StringSetAdd(leafattrib, xstrdup("none"));
585 }
586
587 for (rp = crit; rp != NULL; rp = rp->next)
588 {
589 if (EvalFileResult(RlistScalarValue(rp), leafattrib))
590 {
591 Log(LOG_LEVEL_DEBUG, "Select group match");
592 StringSetDestroy(leafattrib);
593 return true;
594 }
595
596 if (gr && (FullTextMatch(ctx, RlistScalarValue(rp), gr->gr_name)))
597 {
598 Log(LOG_LEVEL_DEBUG, "Select group match");
599 StringSetDestroy(leafattrib);
600 return true;
601 }
602
603 if (FullTextMatch(ctx, RlistScalarValue(rp), buffer))
604 {
605 Log(LOG_LEVEL_DEBUG, "Select group match");
606 StringSetDestroy(leafattrib);
607 return true;
608 }
609 }
610
611 StringSetDestroy(leafattrib);
612 return false;
613 }
614
615 #endif /* !__MINGW32__ */
616