1 /*-
2  * Copyright (c) 2001, 2003 Allan Saddi <allan@saddi.com>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY ALLAN SADDI AND HIS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL ALLAN SADDI OR HIS CONTRIBUTORS BE
18  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24  * POSSIBILITY OF SUCH DAMAGE.
25  *
26  * $Id: ruleset.c 908 2003-12-06 01:01:16Z asaddi $
27  */
28 
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif /* HAVE_CONFIG_H */
32 
33 #include <ctype.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 
38 #if HAVE_INTTYPES_H
39 # include <inttypes.h>
40 #else
41 # if HAVE_STDINT_H
42 #  include <stdint.h>
43 # endif
44 #endif
45 
46 #include "common.h"
47 
48 #include "yafic.h"
49 
50 #ifdef YAFIC_CRYPTO
51 #include "crypto.h"
52 #endif
53 
54 #ifndef lint
55 static const char rcsid[] =
56 	"$Id: ruleset.c 908 2003-12-06 01:01:16Z asaddi $";
57 #endif /* !lint */
58 
59 #define LINE_BUFFER_SIZE 4096
60 
61 static const char flagChars[] = "pinugsamch";
62 static const struct {
63   char c;
64   rflag_t flags;
65 } flagTemplate[] = {
66   { 'R', RFLAG_DEFAULT },
67   { 'L', RFLAG_DEFAULT & ~(RFLAG_MTIME | RFLAG_CTIME | RFLAG_HASH) },
68   { 'N', RFLAG_DEFAULT | RFLAG_SIZE | RFLAG_ATIME },
69   { 'E', 0 }
70 };
71 #define TEMPLATE_COUNT (sizeof (flagTemplate) / sizeof (flagTemplate[0]))
72 
73 static struct RuleEntry **ruleSet;
74 static rflag_t currentMasks[RMASK_MAX];
75 
76 static uint32_t
myhash(const char * str)77 myhash (const char *str)
78 {
79   const uint8_t *s = str;
80   uint32_t h = 0, g;
81 
82   while (*s) {
83     h = (h << 4) + *(s++);
84     if ((g = (h & 0xf0000000)))
85       h ^= g >> 24;
86     h &= ~g;
87   }
88 
89   return h;
90 }
91 
92 void
ApplyRuleSet(void (* func)(struct RuleEntry * re))93 ApplyRuleSet (void (*func) (struct RuleEntry *re))
94 {
95   int i;
96   struct RuleEntry *re;
97 
98   for (i = RULESET_TABLE_SIZE - 1; i >= 0; i--) {
99     re = ruleSet[i];
100     while (re) {
101       func (re);
102       re = re->next;
103     }
104   }
105 }
106 
107 static void
destroyRuleEntry(struct RuleEntry * re)108 destroyRuleEntry (struct RuleEntry *re)
109 {
110   free (re->path);
111   free (re);
112 }
113 
114 struct RuleEntry *
FindRuleEntry(const char * path)115 FindRuleEntry (const char *path)
116 {
117   struct RuleEntry *re;
118   uint32_t hash;
119 
120   hash = myhash (path) % RULESET_TABLE_SIZE;
121   re = ruleSet[hash];
122   while (re) {
123     if (!strcmp (re->path, path))
124       return re;
125     re = re->next;
126   }
127 
128   return NULL;
129 }
130 
131 struct RuleEntry *
FindClosestRuleEntry(const char * path)132 FindClosestRuleEntry (const char *path)
133 {
134   char *newpath;
135   struct RuleEntry *re;
136   char *tmp;
137 
138   newpath = mystrdup (path);
139   /* There better be a root entry or else we're going to loop forever! */
140   for (;;) {
141     if ((re = FindRuleEntry (newpath))) {
142       free (newpath);
143       return re;
144     }
145     tmp = mydirname (newpath);
146     free (newpath);
147     newpath = tmp;
148   }
149   /* NOTREACHED */
150 }
151 
152 static struct RuleEntry *
addRuleEntry(const char * path,rflag_t entryFlags,rflag_t descFlags,rflag_t masks[RMASK_MAX])153 addRuleEntry (const char *path, rflag_t entryFlags, rflag_t descFlags,
154 	      rflag_t masks[RMASK_MAX])
155 {
156   struct RuleEntry *re;
157   int i;
158   uint32_t hash;
159 
160   re = mymalloc (sizeof (*re));
161   re->path = mystrdup (path);
162   re->entryFlags = entryFlags;
163   re->descFlags = descFlags;
164   for (i = 0; i < RMASK_MAX; i++)
165     re->masks[i] = masks[i];
166 
167   hash = myhash (re->path) % RULESET_TABLE_SIZE;
168   re->next = ruleSet[hash];
169   ruleSet[hash] = re;
170 
171   return re;
172 }
173 
174 void
InitRuleSet(void)175 InitRuleSet (void)
176 {
177   int i;
178 
179   /* Initialize hash table. */
180   ruleSet = mymalloc (sizeof (*ruleSet) * RULESET_TABLE_SIZE);
181   memset (ruleSet, 0, sizeof (*ruleSet) * RULESET_TABLE_SIZE);
182 
183   /* Initialize masks. */
184   for (i = 0; i < RMASK_MAX; i++)
185     currentMasks[i] = RMASK_DEFAULT;
186 
187   /* Populate with root entry. */
188   addRuleEntry ("/", RFLAG_DEFAULT, RFLAG_DEFAULT, currentMasks);
189 }
190 
191 void
CleanRuleSet(void)192 CleanRuleSet (void)
193 {
194   int i;
195   struct RuleEntry *re, *nextre;
196 
197   if (!ruleSet)
198     return;
199 
200   for (i = RULESET_TABLE_SIZE - 1; i >= 0; i--) {
201     re = ruleSet[i];
202     while (re) {
203       nextre = re->next;
204       destroyRuleEntry (re);
205       re = nextre;
206     }
207   }
208 
209   free (ruleSet);
210 }
211 
212 static int
parseFlags(const char * flagsStr,rflag_t * flags)213 parseFlags (const char *flagsStr, rflag_t *flags)
214 {
215   int i;
216   int addMode = 1;
217 
218   if (flagsStr[0] == '+' || flagsStr[0] == '-') {
219     *flags = RFLAG_DEFAULT;
220   }
221   else {
222     *flags = 0;
223 
224     for (i = 0; i < TEMPLATE_COUNT; i++) {
225       if (flagTemplate[i].c == *flagsStr) {
226 	*flags = flagTemplate[i].flags;
227 	flagsStr++;
228 	addMode = -1;
229 	break;
230       }
231     }
232   }
233 
234   while (*flagsStr) {
235     if (*flagsStr == '+') {
236       addMode = 1;
237       flagsStr++;
238       continue;
239     }
240     else if (*flagsStr == '-') {
241       addMode = 0;
242       flagsStr++;
243       continue;
244     }
245 
246     for (i = 0; i < RFLAG_MAX; i++)
247       if (*flagsStr == flagChars[i])
248 	break;
249 
250     if (i < RFLAG_MAX) {
251       if (addMode == -1)
252 	return 0;
253       else if (addMode)
254 	*flags |= 1 << i;
255       else
256 	*flags &= ~(1 << i);
257     }
258     else
259       return 0;
260 
261     flagsStr++;
262   }
263 
264   return 1;
265 }
266 
267 static int
checkPath(const char * path)268 checkPath (const char *path)
269 {
270   /* Make sure there are no empty path components. */
271   if (strstr (path, "//"))
272     return 0;
273 
274   /* Make sure it's an absolute path. */
275   if (path[0] != '/')
276     return 0;
277 
278   /* Looks ok. */
279   return 1;
280 }
281 
282 static void
resolveRuleEntry(struct RuleEntry * re)283 resolveRuleEntry (struct RuleEntry *re)
284 {
285   struct RuleEntry *parentre;
286   char *parent, *tmp;
287   int i;
288 
289   if (re->descFlags & RFLAG_UPDATE) {
290     parent = mydirname (re->path);
291 
292     for (;;) {
293       if ((parentre = FindRuleEntry (parent)) &&
294 	  !(parentre->descFlags & RFLAG_IGNORE)) {
295 	re->descFlags = parentre->descFlags;
296 	for (i = 0; i < RMASK_MAX; i++)
297 	  re->masks[i] = parentre->masks[i];
298 	break;
299       }
300       if (!strcmp (parent, "/")) {
301 	/* End of the line, use default. */
302 	re->descFlags = RFLAG_DEFAULT;
303 	for (i = 0; i < RMASK_MAX; i++)
304 	  re->masks[i] = RMASK_DEFAULT;
305 	break;
306       }
307       /* Try next ancestor. */
308       tmp = mydirname (parent);
309       free (parent);
310       parent = tmp;
311     }
312 
313     free (parent);
314   }
315 }
316 
317 static int
parseSpecial(const char * entry,const char * flagsStr)318 parseSpecial (const char *entry, const char *flagsStr)
319 {
320   int classType = -1;
321   rflag_t flags;
322 
323   if (!strcmp (entry, CONFIG_DIRMASK))
324     classType = RMASK_DIR;
325   else if (!strcmp (entry, CONFIG_FILEMASK))
326     classType = RMASK_FILE;
327   else if (!strcmp (entry, CONFIG_LINKMASK))
328     classType = RMASK_LINK;
329   else if (!strcmp (entry, CONFIG_SPECIALMASK))
330     classType = RMASK_SPECIAL;
331 
332   if (classType != -1) {
333     if (!flagsStr)
334       flags = RFLAG_DEFAULT | RFLAG_SIZE | RFLAG_ATIME;
335     else if (!parseFlags (flagsStr, &flags))
336       return -1;
337 
338     currentMasks[classType] = flags;
339     return 1;
340   }
341 
342   return 0;
343 }
344 
345 #define ARGC_MAX 16
346 
347 int
ParseRuleSet(const char * conf)348 ParseRuleSet (const char *conf)
349 {
350   FILE *f;
351   int success = 1;
352 
353   int hardLineNo, lineNo, linePos;
354   char lineBuf[LINE_BUFFER_SIZE], *line;
355   int lineLen;
356   int argc;
357   char *argv[ARGC_MAX];
358   int inQuote, inEsc, quoteErr;
359   char *dst;
360 
361   int i, j, len;
362   char *entry, *flagsStr;
363   rflag_t flags;
364   struct RuleEntry *re, *nextre, *lastre;
365   int ret;
366 
367   if ((f = fopen (conf, "r"))) {
368     if (DisplayHashes > 1) {
369       DisplayFileHash (fileno (f), conf);
370       if (fseek (f, 0, SEEK_SET) == -1)
371 	yaficError (conf);
372     }
373 
374 #ifdef YAFIC_CRYPTO
375     if (SignVerifyFiles) {
376       VerifyFile (fileno (f), conf, NULL);
377       if (fseek (f, 0, SEEK_SET) == -1)
378 	yaficError (conf);
379     }
380 #endif
381 
382     /* Read in the config file, line by line. */
383     hardLineNo = lineNo = 0;
384     linePos = 0;
385     while (!ferror (f) &&
386 	   fgets (&lineBuf[linePos], LINE_BUFFER_SIZE - linePos, f)) {
387       hardLineNo++;
388 
389       /* Eliminate trailing whitespace. */
390       lineLen = strlen (lineBuf) - 1;
391       while (lineLen >= 0 && isspace ((int) lineBuf[lineLen]))
392 	lineBuf[lineLen--] = '\0';
393 
394       /* Keep track of first line of any line continuations. */
395       if (!linePos)
396 	lineNo = hardLineNo;
397 
398       /* See if line should be continued. */
399       if (lineLen >= 0 && lineBuf[lineLen] == '\\') {
400 	lineBuf[lineLen] = '\0';
401 	linePos = lineLen;
402 	continue;
403       }
404 
405       /* Reset line buffer position. */
406       linePos = 0;
407 
408       /* Skip any leading whitespace. */
409       line = lineBuf;
410       while (*line && isspace ((int) *line))
411 	line++;
412 
413       /* Skip over blank lines and comments. */
414       if (!*line || *line == '#')
415 	continue;
416 
417       /* Break apart the line into tokens. */
418 
419       argc = 0;
420       quoteErr = 0;
421       dst = line;
422       while (*line && argc < ARGC_MAX) {
423 	/* Skip any leading whitespace. */
424 	while (*line && isspace ((int) *line))
425 	  line++;
426 
427 	/* If that's it, break out of the loop. */
428 	if (!*line || *line == '#')
429 	  break;
430 
431 	/* Got us a token. */
432 	argv[argc++] = dst;
433 
434 	inQuote = inEsc = 0;
435 	while (*line && (inQuote || !isspace ((int) *line))) {
436 	  if (inEsc) {
437 	    /* Handle an escaped char. For now, whatever follows the
438 	       escape char is simply copied. */
439 	    *(dst++) = *(line++);
440 	    inEsc = 0;
441 	  }
442 	  else if (*line == '"') {
443 	    /* A beginning or ending quote. */
444 	    inQuote ^= 1;
445 	    line++;
446 	  }
447 	  else if (*line == '\\') {
448 	    /* The beginning of an escape sequence. */
449 	    inEsc = 1;
450 	    line++;
451 	  }
452 	  else if (!inQuote && *line == '#') {
453 	    /* Start of a comment. End the token and the line. */
454 	    *line = '\0';
455 	  }
456 	  else /* Everything else. */
457 	    *(dst++) = *(line++);
458 	}
459 
460 	/* Skip over the whitespace, if necessary. */
461 	if (*line)
462 	  line++;
463 
464 	/* End the token. */
465 	*(dst++) = '\0';
466 
467 	if (inQuote) {
468 	  quoteErr = 1;
469 	  break;
470 	}
471       }
472 
473       if (quoteErr) {
474 	fprintf (stderr, "%s: %s:%d: unmatched quote\n",
475 		 prog, conf, lineNo);
476 	success = 0;
477 	continue;
478       }
479 
480       /* Grab entry and flags. */
481       entry = argv[0];
482       flagsStr = argc > 1 ? argv[1] : NULL;
483       if (argc > 2)
484 	fprintf (stderr, "%s: %s:%d: ignoring junk after flags\n",
485 		 prog, conf, lineNo);
486 
487       /* Check for special entries. */
488       if ((ret = parseSpecial(entry, flagsStr)) == 1)
489 	continue;
490       else if (ret == -1) {
491 	fprintf (stderr, "%s: %s:%d: bad flags\n",
492 		 prog, conf, lineNo);
493 	success = 0;
494 	continue;
495       }
496 
497       /* Wipe out trailing slash... except if it's just '/'. */
498       i = strlen (entry) - 1;
499       if (i > 1 && entry[i] == '/')
500 	entry[i] = '\0';
501 
502       /* Make sure entry is correctly formed. */
503       if (entry[0] == '!' || entry[0] == '=' || entry[0] == '$') {
504 	if (!checkPath (&entry[1])) {
505 	  fprintf (stderr, "%s: %s:%d: bad path\n",
506 		   prog, conf, lineNo);
507 	  success = 0;
508 	  continue;
509 	}
510       }
511       else if (!checkPath (entry)) {
512 	fprintf (stderr, "%s: %s:%d: bad path\n",
513 		 prog, conf, lineNo);
514 	success = 0;
515 	continue;
516       }
517 
518       if (!flagsStr)
519 	flags = RFLAG_DEFAULT;
520       else if (!parseFlags (flagsStr, &flags)) {
521 	fprintf (stderr, "%s: %s:%d: bad flags\n",
522 		 prog, conf, lineNo);
523 	success = 0;
524 	continue;
525       }
526 
527       /* Parse the entry. */
528       if (*entry == '!') {
529 	/* Ignore entry. */
530 	entry++;
531 	/* Wipe out existing entries. */
532 	len = strlen (entry);
533 	for (i = 0; i < RULESET_TABLE_SIZE; i++) {
534 	  lastre = NULL;
535 	  re = ruleSet[i];
536 	  while (re) {
537 	    nextre = re->next;
538 	    if (!strncmp (entry, re->path, len)) {
539 	      if (!lastre)
540 		ruleSet[i] = nextre;
541 	      else
542 		lastre->next = nextre;
543 	      destroyRuleEntry (re);
544 	    }
545 	    else
546 	      lastre = re;
547 	    re = nextre;
548 	  }
549 	}
550 	addRuleEntry (entry, RFLAG_IGNORE, RFLAG_IGNORE, currentMasks);
551       }
552       else if (*entry == '=') {
553 	/* Directory only, no recursion. */
554 	entry++;
555 	if ((re = FindRuleEntry (entry))) {
556 	  re->entryFlags = flags;
557 	  re->descFlags = RFLAG_IGNORE;
558 	}
559 	else
560 	  addRuleEntry (entry, flags, RFLAG_IGNORE, currentMasks);
561       }
562       else if (*entry == '$') {
563 	/* Directory only, recurse. */
564 	entry++;
565 	if ((re = FindRuleEntry (entry)))
566 	  re->entryFlags = flags;
567 	else
568 	  addRuleEntry (entry, flags, RFLAG_UPDATE, currentMasks);
569       }
570       else {
571 	/* Normal entry. */
572 	if ((re = FindRuleEntry (entry))) {
573 	  re->entryFlags = flags;
574 	  re->descFlags = flags;
575 	  for (j = 0; j < RMASK_MAX; j++)
576 	    re->masks[j] = currentMasks[j];
577 	}
578 	else
579 	  addRuleEntry (entry, flags, flags, currentMasks);
580       }
581     }
582     if (ferror (f))
583       yaficError (conf);
584 
585     if (linePos) {
586       fprintf (stderr, "%s: %s:%d: incomplete line continuation\n",
587 	       prog, conf, lineNo);
588       success = 0;
589     }
590   }
591   else
592     yaficError (conf);
593 
594   if (success)
595     ApplyRuleSet (resolveRuleEntry);
596 
597   return success;
598 }
599 
600 static char *
dumpFlags(rflag_t flags)601 dumpFlags (rflag_t flags)
602 {
603   char *p;
604   int i;
605   rflag_t test;
606   static char buf[16];
607 
608   if (flags & RFLAG_IGNORE) {
609     buf[0] = '-';
610     buf[1] = '\0';
611     return buf;
612   }
613 
614   p = buf;
615   for (i = 0, test = 1; i < RFLAG_MAX; i++, test <<= 1)
616     if (flags & test)
617       *(p++) = flagChars[i];
618   *(p++) = '\0';
619 
620   return buf;
621 }
622 
623 void
DumpRuleEntry(struct RuleEntry * re)624 DumpRuleEntry (struct RuleEntry *re)
625 {
626   printf ("  %s (%s, ", re->path, dumpFlags (re->entryFlags));
627   printf ("%s) (", dumpFlags (re->descFlags));
628   printf ("d:%s ", dumpFlags (re->masks[RMASK_DIR]));
629   printf ("f:%s ", dumpFlags (re->masks[RMASK_FILE]));
630   printf ("l:%s ", dumpFlags (re->masks[RMASK_LINK]));
631   printf ("s:%s)\n", dumpFlags (re->masks[RMASK_SPECIAL]));
632 }
633