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