/*- * Copyright (c) 2001, 2003 Allan Saddi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY ALLAN SADDI AND HIS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL ALLAN SADDI OR HIS CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * $Id: ruleset.c 908 2003-12-06 01:01:16Z asaddi $ */ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #include #include #include #if HAVE_INTTYPES_H # include #else # if HAVE_STDINT_H # include # endif #endif #include "common.h" #include "yafic.h" #ifdef YAFIC_CRYPTO #include "crypto.h" #endif #ifndef lint static const char rcsid[] = "$Id: ruleset.c 908 2003-12-06 01:01:16Z asaddi $"; #endif /* !lint */ #define LINE_BUFFER_SIZE 4096 static const char flagChars[] = "pinugsamch"; static const struct { char c; rflag_t flags; } flagTemplate[] = { { 'R', RFLAG_DEFAULT }, { 'L', RFLAG_DEFAULT & ~(RFLAG_MTIME | RFLAG_CTIME | RFLAG_HASH) }, { 'N', RFLAG_DEFAULT | RFLAG_SIZE | RFLAG_ATIME }, { 'E', 0 } }; #define TEMPLATE_COUNT (sizeof (flagTemplate) / sizeof (flagTemplate[0])) static struct RuleEntry **ruleSet; static rflag_t currentMasks[RMASK_MAX]; static uint32_t myhash (const char *str) { const uint8_t *s = str; uint32_t h = 0, g; while (*s) { h = (h << 4) + *(s++); if ((g = (h & 0xf0000000))) h ^= g >> 24; h &= ~g; } return h; } void ApplyRuleSet (void (*func) (struct RuleEntry *re)) { int i; struct RuleEntry *re; for (i = RULESET_TABLE_SIZE - 1; i >= 0; i--) { re = ruleSet[i]; while (re) { func (re); re = re->next; } } } static void destroyRuleEntry (struct RuleEntry *re) { free (re->path); free (re); } struct RuleEntry * FindRuleEntry (const char *path) { struct RuleEntry *re; uint32_t hash; hash = myhash (path) % RULESET_TABLE_SIZE; re = ruleSet[hash]; while (re) { if (!strcmp (re->path, path)) return re; re = re->next; } return NULL; } struct RuleEntry * FindClosestRuleEntry (const char *path) { char *newpath; struct RuleEntry *re; char *tmp; newpath = mystrdup (path); /* There better be a root entry or else we're going to loop forever! */ for (;;) { if ((re = FindRuleEntry (newpath))) { free (newpath); return re; } tmp = mydirname (newpath); free (newpath); newpath = tmp; } /* NOTREACHED */ } static struct RuleEntry * addRuleEntry (const char *path, rflag_t entryFlags, rflag_t descFlags, rflag_t masks[RMASK_MAX]) { struct RuleEntry *re; int i; uint32_t hash; re = mymalloc (sizeof (*re)); re->path = mystrdup (path); re->entryFlags = entryFlags; re->descFlags = descFlags; for (i = 0; i < RMASK_MAX; i++) re->masks[i] = masks[i]; hash = myhash (re->path) % RULESET_TABLE_SIZE; re->next = ruleSet[hash]; ruleSet[hash] = re; return re; } void InitRuleSet (void) { int i; /* Initialize hash table. */ ruleSet = mymalloc (sizeof (*ruleSet) * RULESET_TABLE_SIZE); memset (ruleSet, 0, sizeof (*ruleSet) * RULESET_TABLE_SIZE); /* Initialize masks. */ for (i = 0; i < RMASK_MAX; i++) currentMasks[i] = RMASK_DEFAULT; /* Populate with root entry. */ addRuleEntry ("/", RFLAG_DEFAULT, RFLAG_DEFAULT, currentMasks); } void CleanRuleSet (void) { int i; struct RuleEntry *re, *nextre; if (!ruleSet) return; for (i = RULESET_TABLE_SIZE - 1; i >= 0; i--) { re = ruleSet[i]; while (re) { nextre = re->next; destroyRuleEntry (re); re = nextre; } } free (ruleSet); } static int parseFlags (const char *flagsStr, rflag_t *flags) { int i; int addMode = 1; if (flagsStr[0] == '+' || flagsStr[0] == '-') { *flags = RFLAG_DEFAULT; } else { *flags = 0; for (i = 0; i < TEMPLATE_COUNT; i++) { if (flagTemplate[i].c == *flagsStr) { *flags = flagTemplate[i].flags; flagsStr++; addMode = -1; break; } } } while (*flagsStr) { if (*flagsStr == '+') { addMode = 1; flagsStr++; continue; } else if (*flagsStr == '-') { addMode = 0; flagsStr++; continue; } for (i = 0; i < RFLAG_MAX; i++) if (*flagsStr == flagChars[i]) break; if (i < RFLAG_MAX) { if (addMode == -1) return 0; else if (addMode) *flags |= 1 << i; else *flags &= ~(1 << i); } else return 0; flagsStr++; } return 1; } static int checkPath (const char *path) { /* Make sure there are no empty path components. */ if (strstr (path, "//")) return 0; /* Make sure it's an absolute path. */ if (path[0] != '/') return 0; /* Looks ok. */ return 1; } static void resolveRuleEntry (struct RuleEntry *re) { struct RuleEntry *parentre; char *parent, *tmp; int i; if (re->descFlags & RFLAG_UPDATE) { parent = mydirname (re->path); for (;;) { if ((parentre = FindRuleEntry (parent)) && !(parentre->descFlags & RFLAG_IGNORE)) { re->descFlags = parentre->descFlags; for (i = 0; i < RMASK_MAX; i++) re->masks[i] = parentre->masks[i]; break; } if (!strcmp (parent, "/")) { /* End of the line, use default. */ re->descFlags = RFLAG_DEFAULT; for (i = 0; i < RMASK_MAX; i++) re->masks[i] = RMASK_DEFAULT; break; } /* Try next ancestor. */ tmp = mydirname (parent); free (parent); parent = tmp; } free (parent); } } static int parseSpecial (const char *entry, const char *flagsStr) { int classType = -1; rflag_t flags; if (!strcmp (entry, CONFIG_DIRMASK)) classType = RMASK_DIR; else if (!strcmp (entry, CONFIG_FILEMASK)) classType = RMASK_FILE; else if (!strcmp (entry, CONFIG_LINKMASK)) classType = RMASK_LINK; else if (!strcmp (entry, CONFIG_SPECIALMASK)) classType = RMASK_SPECIAL; if (classType != -1) { if (!flagsStr) flags = RFLAG_DEFAULT | RFLAG_SIZE | RFLAG_ATIME; else if (!parseFlags (flagsStr, &flags)) return -1; currentMasks[classType] = flags; return 1; } return 0; } #define ARGC_MAX 16 int ParseRuleSet (const char *conf) { FILE *f; int success = 1; int hardLineNo, lineNo, linePos; char lineBuf[LINE_BUFFER_SIZE], *line; int lineLen; int argc; char *argv[ARGC_MAX]; int inQuote, inEsc, quoteErr; char *dst; int i, j, len; char *entry, *flagsStr; rflag_t flags; struct RuleEntry *re, *nextre, *lastre; int ret; if ((f = fopen (conf, "r"))) { if (DisplayHashes > 1) { DisplayFileHash (fileno (f), conf); if (fseek (f, 0, SEEK_SET) == -1) yaficError (conf); } #ifdef YAFIC_CRYPTO if (SignVerifyFiles) { VerifyFile (fileno (f), conf, NULL); if (fseek (f, 0, SEEK_SET) == -1) yaficError (conf); } #endif /* Read in the config file, line by line. */ hardLineNo = lineNo = 0; linePos = 0; while (!ferror (f) && fgets (&lineBuf[linePos], LINE_BUFFER_SIZE - linePos, f)) { hardLineNo++; /* Eliminate trailing whitespace. */ lineLen = strlen (lineBuf) - 1; while (lineLen >= 0 && isspace ((int) lineBuf[lineLen])) lineBuf[lineLen--] = '\0'; /* Keep track of first line of any line continuations. */ if (!linePos) lineNo = hardLineNo; /* See if line should be continued. */ if (lineLen >= 0 && lineBuf[lineLen] == '\\') { lineBuf[lineLen] = '\0'; linePos = lineLen; continue; } /* Reset line buffer position. */ linePos = 0; /* Skip any leading whitespace. */ line = lineBuf; while (*line && isspace ((int) *line)) line++; /* Skip over blank lines and comments. */ if (!*line || *line == '#') continue; /* Break apart the line into tokens. */ argc = 0; quoteErr = 0; dst = line; while (*line && argc < ARGC_MAX) { /* Skip any leading whitespace. */ while (*line && isspace ((int) *line)) line++; /* If that's it, break out of the loop. */ if (!*line || *line == '#') break; /* Got us a token. */ argv[argc++] = dst; inQuote = inEsc = 0; while (*line && (inQuote || !isspace ((int) *line))) { if (inEsc) { /* Handle an escaped char. For now, whatever follows the escape char is simply copied. */ *(dst++) = *(line++); inEsc = 0; } else if (*line == '"') { /* A beginning or ending quote. */ inQuote ^= 1; line++; } else if (*line == '\\') { /* The beginning of an escape sequence. */ inEsc = 1; line++; } else if (!inQuote && *line == '#') { /* Start of a comment. End the token and the line. */ *line = '\0'; } else /* Everything else. */ *(dst++) = *(line++); } /* Skip over the whitespace, if necessary. */ if (*line) line++; /* End the token. */ *(dst++) = '\0'; if (inQuote) { quoteErr = 1; break; } } if (quoteErr) { fprintf (stderr, "%s: %s:%d: unmatched quote\n", prog, conf, lineNo); success = 0; continue; } /* Grab entry and flags. */ entry = argv[0]; flagsStr = argc > 1 ? argv[1] : NULL; if (argc > 2) fprintf (stderr, "%s: %s:%d: ignoring junk after flags\n", prog, conf, lineNo); /* Check for special entries. */ if ((ret = parseSpecial(entry, flagsStr)) == 1) continue; else if (ret == -1) { fprintf (stderr, "%s: %s:%d: bad flags\n", prog, conf, lineNo); success = 0; continue; } /* Wipe out trailing slash... except if it's just '/'. */ i = strlen (entry) - 1; if (i > 1 && entry[i] == '/') entry[i] = '\0'; /* Make sure entry is correctly formed. */ if (entry[0] == '!' || entry[0] == '=' || entry[0] == '$') { if (!checkPath (&entry[1])) { fprintf (stderr, "%s: %s:%d: bad path\n", prog, conf, lineNo); success = 0; continue; } } else if (!checkPath (entry)) { fprintf (stderr, "%s: %s:%d: bad path\n", prog, conf, lineNo); success = 0; continue; } if (!flagsStr) flags = RFLAG_DEFAULT; else if (!parseFlags (flagsStr, &flags)) { fprintf (stderr, "%s: %s:%d: bad flags\n", prog, conf, lineNo); success = 0; continue; } /* Parse the entry. */ if (*entry == '!') { /* Ignore entry. */ entry++; /* Wipe out existing entries. */ len = strlen (entry); for (i = 0; i < RULESET_TABLE_SIZE; i++) { lastre = NULL; re = ruleSet[i]; while (re) { nextre = re->next; if (!strncmp (entry, re->path, len)) { if (!lastre) ruleSet[i] = nextre; else lastre->next = nextre; destroyRuleEntry (re); } else lastre = re; re = nextre; } } addRuleEntry (entry, RFLAG_IGNORE, RFLAG_IGNORE, currentMasks); } else if (*entry == '=') { /* Directory only, no recursion. */ entry++; if ((re = FindRuleEntry (entry))) { re->entryFlags = flags; re->descFlags = RFLAG_IGNORE; } else addRuleEntry (entry, flags, RFLAG_IGNORE, currentMasks); } else if (*entry == '$') { /* Directory only, recurse. */ entry++; if ((re = FindRuleEntry (entry))) re->entryFlags = flags; else addRuleEntry (entry, flags, RFLAG_UPDATE, currentMasks); } else { /* Normal entry. */ if ((re = FindRuleEntry (entry))) { re->entryFlags = flags; re->descFlags = flags; for (j = 0; j < RMASK_MAX; j++) re->masks[j] = currentMasks[j]; } else addRuleEntry (entry, flags, flags, currentMasks); } } if (ferror (f)) yaficError (conf); if (linePos) { fprintf (stderr, "%s: %s:%d: incomplete line continuation\n", prog, conf, lineNo); success = 0; } } else yaficError (conf); if (success) ApplyRuleSet (resolveRuleEntry); return success; } static char * dumpFlags (rflag_t flags) { char *p; int i; rflag_t test; static char buf[16]; if (flags & RFLAG_IGNORE) { buf[0] = '-'; buf[1] = '\0'; return buf; } p = buf; for (i = 0, test = 1; i < RFLAG_MAX; i++, test <<= 1) if (flags & test) *(p++) = flagChars[i]; *(p++) = '\0'; return buf; } void DumpRuleEntry (struct RuleEntry *re) { printf (" %s (%s, ", re->path, dumpFlags (re->entryFlags)); printf ("%s) (", dumpFlags (re->descFlags)); printf ("d:%s ", dumpFlags (re->masks[RMASK_DIR])); printf ("f:%s ", dumpFlags (re->masks[RMASK_FILE])); printf ("l:%s ", dumpFlags (re->masks[RMASK_LINK])); printf ("s:%s)\n", dumpFlags (re->masks[RMASK_SPECIAL])); }