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