"Fossies" - the Fresh Open Source Software Archive

Member "logrotate-3.18.1/config.c" (21 May 2021, 78607 Bytes) of package /linux/privat/logrotate-3.18.1.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "config.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.18.0_vs_3.18.1.

    1 #include "queue.h"
    2 #include <limits.h>
    3 #include <ctype.h>
    4 #include <dirent.h>
    5 #include <errno.h>
    6 #include <fcntl.h>
    7 #include <glob.h>
    8 #include <grp.h>
    9 #include <popt.h>
   10 #include <pwd.h>
   11 #include <stdio.h>
   12 #include <stdlib.h>
   13 #include <string.h>
   14 #include <sys/stat.h>
   15 #include <unistd.h>
   16 #include <assert.h>
   17 #include <wchar.h>
   18 #include <wctype.h>
   19 #include <fnmatch.h>
   20 #include <sys/mman.h>
   21 #include <libgen.h>
   22 
   23 #if !defined(PATH_MAX) && defined(__FreeBSD__)
   24 #include <sys/param.h>
   25 #endif
   26 
   27 #include "log.h"
   28 #include "logrotate.h"
   29 
   30 struct logInfoHead logs;
   31 
   32 #if !defined(GLOB_ABORTED) && defined(GLOB_ABEND)
   33 #define GLOB_ABORTED GLOB_ABEND
   34 #endif
   35 
   36 #define REALLOC_STEP            10
   37 #define GLOB_STR_REALLOC_STEP   0x100
   38 
   39 #if defined(SunOS) && !defined(isblank)
   40 #define isblank(c) ( ( (c) == ' ' || (c) == '\t' ) ? 1 : 0 )
   41 #endif
   42 
   43 #ifdef __hpux
   44 #include "asprintf.c"
   45 #endif
   46 
   47 #if !defined(HAVE_SECURE_GETENV)
   48 #define secure_getenv getenv
   49 #endif
   50 
   51 #if !defined(HAVE_ASPRINTF) && !defined(_FORTIFY_SOURCE)
   52 #include <stdarg.h>
   53 
   54 int asprintf(char **string_ptr, const char *format, ...)
   55 {
   56     va_list arg;
   57     char *str;
   58     int size;
   59     int rv;
   60 
   61     va_start(arg, format);
   62     size = vsnprintf(NULL, 0, format, arg);
   63     va_end(arg);
   64     if (size < 0) {
   65         return -1;
   66     }
   67     str = malloc((size_t)size + 1);
   68     if (str == NULL) {
   69         return -1;
   70     }
   71     va_start(arg, format);
   72     rv = vsnprintf(str, (size_t)size + 1, format, arg);
   73     va_end(arg);
   74 
   75     *string_ptr = str;
   76     return rv;
   77 }
   78 
   79 #endif
   80 
   81 #if !defined(HAVE_STRNDUP)
   82 char *strndup(const char *s, size_t n)
   83 {
   84     size_t nAvail;
   85     char *p;
   86 
   87     /* min() */
   88     nAvail = strlen(s) + 1;
   89     if ( (n + 1) < nAvail)
   90         nAvail = n + 1;
   91 
   92     p = malloc(nAvail);
   93     if (!p)
   94         return NULL;
   95     memcpy(p, s, nAvail);
   96     p[nAvail - 1] = 0;
   97     return p;
   98 }
   99 #endif
  100 
  101 /* list of compression commands and the corresponding file extensions */
  102 struct compress_cmd_item {
  103     const char *cmd;
  104     const char *ext;
  105 };
  106 static const struct compress_cmd_item compress_cmd_list[] = {
  107     {"gzip", ".gz"},
  108     {"bzip2", ".bz2"},
  109     {"xz", ".xz"},
  110     {"zstd", ".zst"},
  111     {"compress", ".Z"},
  112     {"zip", "zip"},
  113 };
  114 static const unsigned compress_cmd_list_size = sizeof(compress_cmd_list)
  115     / sizeof(compress_cmd_list[0]);
  116 
  117 enum {
  118     STATE_DEFAULT = 2,
  119     STATE_SKIP_LINE = 4,
  120     STATE_DEFINITION_END = 8,
  121     STATE_SKIP_CONFIG = 16,
  122     STATE_LOAD_SCRIPT = 32,
  123     STATE_ERROR = 64,
  124 };
  125 
  126 static const char *defTabooExts[] = {
  127     ",v",
  128     ".bak",
  129     ".cfsaved",
  130     ".disabled",
  131     ".dpkg-bak",
  132     ".dpkg-del",
  133     ".dpkg-dist",
  134     ".dpkg-new",
  135     ".dpkg-old",
  136     ".dpkg-tmp",
  137     ".rhn-cfg-tmp-*",
  138     ".rpmnew",
  139     ".rpmorig",
  140     ".rpmsave",
  141     ".swp",
  142     ".ucf-dist",
  143     ".ucf-new",
  144     ".ucf-old",
  145     "~"
  146 };
  147 static const unsigned defTabooCount = sizeof(defTabooExts) / sizeof(char *);
  148 
  149 /* I shouldn't use globals here :-( */
  150 static char **tabooPatterns = NULL;
  151 static unsigned tabooCount = 0;
  152 static int glob_errno = 0;
  153 
  154 static int readConfigFile(const char *configFile, struct logInfo *defConfig);
  155 static int globerr(const char *pathname, int theerr);
  156 
  157 static char *isolateLine(char **strt, char **buf, size_t length) {
  158     char *endtag, *start, *tmp;
  159     const char *max = *buf + length;
  160     char *key;
  161 
  162     start = *strt;
  163     endtag = start;
  164     while (endtag < max && *endtag != '\n') {
  165         endtag++;}
  166     if (max < endtag)
  167         return NULL;
  168     tmp = endtag - 1;
  169     while (isspace((unsigned char)*endtag))
  170         endtag--;
  171     key = strndup(start, (size_t)(endtag - start + 1));
  172     if (key == NULL) {
  173         message_OOM();
  174         return NULL;
  175     }
  176     *strt = tmp;
  177     return key;
  178 }
  179 
  180 static char *isolateValue(const char *fileName, int lineNum, const char *key,
  181                           char **startPtr, char **buf, size_t length)
  182 {
  183     char *chptr = *startPtr;
  184     const char *max = *startPtr + length;
  185 
  186     while (chptr < max && isblank((unsigned char)*chptr))
  187         chptr++;
  188     if (chptr < max && *chptr == '=') {
  189         chptr++;
  190         while ( chptr < max && isblank((unsigned char)*chptr))
  191             chptr++;
  192     }
  193 
  194     if (chptr < max && *chptr == '\n') {
  195         message(MESS_ERROR, "%s:%d argument expected after %s\n",
  196                 fileName, lineNum, key);
  197         return NULL;
  198     }
  199 
  200     *startPtr = chptr;
  201     return isolateLine(startPtr, buf, length);
  202 }
  203 
  204 static char *isolateWord(char **strt, char **buf, size_t length) {
  205     char *endtag, *start;
  206     const char *max = *buf + length;
  207     char *key;
  208     start = *strt;
  209     while (start < max && isblank((unsigned char)*start))
  210         start++;
  211     endtag = start;
  212     while (endtag < max && isalpha((unsigned char)*endtag)) {
  213         endtag++;}
  214     if (max < endtag)
  215         return NULL;
  216     key = strndup(start, (size_t)(endtag - start));
  217     if (key == NULL) {
  218         message_OOM();
  219         return NULL;
  220     }
  221     *strt = endtag;
  222     return key;
  223 }
  224 
  225 static char *readPath(const char *configFile, int lineNum, const char *key,
  226                       char **startPtr, char **buf, size_t length)
  227 {
  228     char *path = isolateValue(configFile, lineNum, key, startPtr, buf, length);
  229     if (path != NULL) {
  230         wchar_t pwc;
  231         size_t len;
  232         const char *chptr = path;
  233 
  234         while (*chptr && (len = mbrtowc(&pwc, chptr, strlen(chptr), NULL)) != 0) {
  235             if (len == (size_t)(-1) || len == (size_t)(-2) || !iswprint((wint_t)pwc) || iswblank((wint_t)pwc)) {
  236                 message(MESS_ERROR, "%s:%d bad %s path %s\n",
  237                         configFile, lineNum, key, path);
  238                 free(path);
  239                 return NULL;
  240             }
  241             chptr += len;
  242         }
  243     }
  244     return path;
  245 }
  246 
  247 /* set *pUid to UID of the given user, return non-zero on failure */
  248 static int resolveUid(const char *userName, uid_t *pUid)
  249 {
  250     const struct passwd *pw;
  251     char *endptr;
  252     unsigned long int parsed_uid;
  253 
  254 #ifdef __CYGWIN__
  255     if (strcmp(userName, "root") == 0) {
  256         *pUid = 0;
  257         return 0;
  258     }
  259 #endif
  260 
  261     pw = getpwnam(userName);
  262     if (pw) {
  263         *pUid = pw->pw_uid;
  264         return 0;
  265     }
  266 
  267     parsed_uid = strtoul(userName, &endptr, 10);
  268     if (userName[0] != '\0' &&
  269         *endptr == '\0' &&
  270         parsed_uid < INT_MAX && /* parsed_uid != ULONG_MAX && */
  271         getpwuid((uid_t)parsed_uid) != NULL) {
  272 
  273         *pUid = (uid_t)parsed_uid;
  274         return 0;
  275     }
  276 
  277     return -1;
  278 }
  279 
  280 /* set *pGid to GID of the given group, return non-zero on failure */
  281 static int resolveGid(const char *groupName, gid_t *pGid)
  282 {
  283     const struct group *gr;
  284     char *endptr;
  285     unsigned long int parsed_gid;
  286 
  287 #ifdef __CYGWIN__
  288     if (strcmp(groupName, "root") == 0) {
  289         *pGid = 0;
  290         return 0;
  291     }
  292 #endif
  293 
  294     gr = getgrnam(groupName);
  295     if (gr) {
  296         *pGid = gr->gr_gid;
  297         return 0;
  298     }
  299 
  300     parsed_gid = strtoul(groupName, &endptr, 10);
  301     if (groupName[0] != '\0' &&
  302         *endptr == '\0' &&
  303         parsed_gid < INT_MAX && /* parsed_gid != ULONG_MAX && */
  304         getgrgid((gid_t)parsed_gid) != NULL) {
  305 
  306         *pGid = (gid_t)parsed_gid;
  307         return 0;
  308     }
  309 
  310     return -1;
  311 }
  312 
  313 static int readModeUidGid(const char *configFile, int lineNum, const char *key,
  314                           const char *directive, mode_t *mode, uid_t *pUid,
  315                           gid_t *pGid)
  316 {
  317     char u[200], g[200];
  318     mode_t m = 0;
  319     char tmp;
  320     int rc;
  321 
  322     if (!strcmp("su", directive))
  323         /* do not read <mode> for the 'su' directive */
  324         rc = 0;
  325     else {
  326         unsigned short int parsed_mode;
  327         rc = sscanf(key, "%ho %199s %199s%c", &parsed_mode, u, g, &tmp);
  328         m = parsed_mode;
  329     }
  330 
  331     /* We support 'key <owner> <group> notation now */
  332     if (rc == 0) {
  333         rc = sscanf(key, "%199s %199s%c", u, g, &tmp);
  334         /* Simulate that we have read mode and keep the default value. */
  335         if (rc > 0) {
  336             m = *mode;
  337             rc += 1;
  338         }
  339     }
  340 
  341     if (rc == 4) {
  342         message(MESS_ERROR, "%s:%d extra arguments for "
  343                 "%s\n", configFile, lineNum, directive);
  344         return -1;
  345     }
  346 
  347     if (rc > 0) {
  348         *mode = m;
  349     }
  350 
  351     if (rc > 1) {
  352         if (resolveUid(u, pUid) != 0) {
  353             message(MESS_ERROR, "%s:%d unknown user '%s'\n",
  354                     configFile, lineNum, u);
  355             return -1;
  356         }
  357     }
  358     if (rc > 2) {
  359         if (resolveGid(g, pGid) != 0) {
  360             message(MESS_ERROR, "%s:%d unknown group '%s'\n",
  361                     configFile, lineNum, g);
  362             return -1;
  363         }
  364     }
  365 
  366     return 0;
  367 }
  368 
  369 static char *readAddress(const char *configFile, int lineNum, const char *key,
  370                          char **startPtr, char **buf, size_t length)
  371 {
  372     char *start = *startPtr;
  373     char *address = isolateValue(configFile, lineNum, key, startPtr, buf, length);
  374 
  375     if (address != NULL) {
  376         /* validate the address */
  377         const char *chptr = address;
  378         while (isprint((unsigned char) *chptr) && *chptr != ' ') {
  379             chptr++;
  380         }
  381 
  382         if (*chptr) {
  383             message(MESS_ERROR, "%s:%d bad %s address %s\n",
  384                     configFile, lineNum, key, start);
  385             free(address);
  386             return NULL;
  387         }
  388     }
  389 
  390     return address;
  391 }
  392 
  393 static int do_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid) {
  394     if (mkdir(path, mode) == 0) {
  395         /* newly created directory, set the owner and permissions */
  396         if (chown(path, uid, gid) != 0) {
  397             message(MESS_ERROR, "error setting owner of %s to uid %u and gid %u: %s\n",
  398                     path, (unsigned) uid, (unsigned) gid, strerror(errno));
  399             return -1;
  400         }
  401 
  402         if (chmod(path, mode) != 0) {
  403             message(MESS_ERROR, "error setting permissions of %s to 0%o: %s\n",
  404                     path, mode, strerror(errno));
  405             return -1;
  406         }
  407 
  408         return 0;
  409     }
  410 
  411     if (errno == EEXIST) {
  412         /* path already exists, check whether it is a directory or not */
  413         struct stat sb;
  414         if ((stat(path, &sb) == 0) && S_ISDIR(sb.st_mode))
  415             return 0;
  416 
  417         message(MESS_ERROR, "path %s already exists, but it is not a directory\n", path);
  418         errno = ENOTDIR;
  419         return -1;
  420     }
  421 
  422     message(MESS_ERROR, "error creating %s: %s\n", path, strerror(errno));
  423     return -1;
  424 }
  425 
  426 static int mkpath(const char *path, mode_t mode, uid_t uid, gid_t gid) {
  427     char *pp;
  428     char *sp;
  429     int rv;
  430     char *copypath = strdup(path);
  431 
  432     if (!copypath) {
  433         message_OOM();
  434         return 1;
  435     }
  436 
  437     rv = 0;
  438     pp = copypath;
  439     while (rv == 0 && (sp = strchr(pp, '/')) != NULL) {
  440         if (sp != pp) {
  441             *sp = '\0';
  442             rv = do_mkdir(copypath, mode, uid, gid);
  443             *sp = '/';
  444         }
  445         pp = sp + 1;
  446     }
  447     if (rv == 0) {
  448         rv = do_mkdir(path, mode, uid, gid);
  449     }
  450     free(copypath);
  451     return rv;
  452 }
  453 
  454 static int checkFile(const char *fname)
  455 {
  456     unsigned i;
  457 
  458     /* Check if fname is '.' or '..'; if so, return false */
  459     if (fname[0] == '.' && (!fname[1] || (fname[1] == '.' && !fname[2])))
  460         return 0;
  461 
  462     /* Check if fname is ending in a taboo-extension; if so, return false */
  463     for (i = 0; i < tabooCount; i++) {
  464         const char *pattern = tabooPatterns[i];
  465         if (!fnmatch(pattern, fname, FNM_PERIOD))
  466         {
  467             message(MESS_DEBUG, "Ignoring %s, because of %s pattern match\n",
  468                     fname, pattern);
  469             return 0;
  470         }
  471     }
  472     /* All checks have been passed; return true */
  473     return 1;
  474 }
  475 
  476 /* Used by qsort to sort filelist */
  477 static int compar(const void *p, const void *q)
  478 {
  479     return strcoll(*((char * const*) p), *((char * const*) q));
  480 }
  481 
  482 /* Free memory blocks pointed to by pointers in a 2d array and the array itself */
  483 static void free_2d_array(char **array, unsigned lines_count)
  484 {
  485     unsigned i;
  486     for (i = 0; i < lines_count; ++i)
  487         free(array[i]);
  488     free(array);
  489 }
  490 
  491 #define MEMBER_COPY(dest, src) \
  492     do { \
  493         if ((src) && rv == 0) { \
  494             (dest) = strdup(src); \
  495             if ((dest) == NULL) { \
  496                 message_OOM(); \
  497                 rv = 1; \
  498             } \
  499         } else { \
  500             (dest) = NULL; \
  501         } \
  502     } while (0)
  503 static int copyLogInfo(struct logInfo *to, const struct logInfo *from)
  504 {
  505     int rv = 0;
  506 
  507     memset(to, 0, sizeof(*to));
  508     MEMBER_COPY(to->oldDir, from->oldDir);
  509     to->criterium = from->criterium;
  510     to->weekday = from->weekday;
  511     to->threshold = from->threshold;
  512     to->minsize = from->minsize;
  513     to->maxsize = from->maxsize;
  514     to->rotateCount = from->rotateCount;
  515     to->rotateMinAge = from->rotateMinAge;
  516     to->rotateAge = from->rotateAge;
  517     to->logStart = from->logStart;
  518     MEMBER_COPY(to->pre, from->pre);
  519     MEMBER_COPY(to->post, from->post);
  520     MEMBER_COPY(to->first, from->first);
  521     MEMBER_COPY(to->last, from->last);
  522     MEMBER_COPY(to->preremove, from->preremove);
  523     MEMBER_COPY(to->logAddress , from->logAddress);
  524     MEMBER_COPY(to->extension, from->extension);
  525     MEMBER_COPY(to->compress_prog, from->compress_prog);
  526     MEMBER_COPY(to->uncompress_prog, from->uncompress_prog);
  527     MEMBER_COPY(to->compress_ext, from->compress_ext);
  528     to->flags = from->flags;
  529     to->shred_cycles = from->shred_cycles;
  530     to->createMode = from->createMode;
  531     to->createUid = from->createUid;
  532     to->createGid = from->createGid;
  533     to->suUid = from->suUid;
  534     to->suGid = from->suGid;
  535     to->olddirMode = from->olddirMode;
  536     to->olddirUid = from->olddirUid;
  537     to->olddirGid = from->olddirGid;
  538 
  539     if (from->compress_options_count) {
  540         poptDupArgv(from->compress_options_count, from->compress_options_list,
  541                     &to->compress_options_count,  &to->compress_options_list);
  542         if (to->compress_options_list == NULL) {
  543             message_OOM();
  544             rv = 1;
  545         }
  546     }
  547 
  548     MEMBER_COPY(to->dateformat, from->dateformat);
  549 
  550     to->list = from->list;
  551 
  552     return rv;
  553 }
  554 #undef MEMBER_COPY
  555 
  556 static void freeLogInfo(struct logInfo *log)
  557 {
  558     free(log->pattern);
  559     free_2d_array(log->files, log->numFiles);
  560     free(log->oldDir);
  561     free(log->pre);
  562     free(log->post);
  563     free(log->first);
  564     free(log->last);
  565     free(log->preremove);
  566     free(log->logAddress);
  567     free(log->extension);
  568     free(log->compress_prog);
  569     free(log->uncompress_prog);
  570     free(log->compress_ext);
  571     free(log->compress_options_list);
  572     free(log->dateformat);
  573 }
  574 
  575 static struct logInfo *newLogInfo(const struct logInfo *template)
  576 {
  577     struct logInfo *new;
  578 
  579     new = malloc(sizeof(*new));
  580     if (new == NULL) {
  581         message_OOM();
  582         return NULL;
  583     }
  584 
  585     if (copyLogInfo(new, template)) {
  586         freeLogInfo(new);
  587         free(new);
  588         return NULL;
  589     }
  590 
  591     TAILQ_INSERT_TAIL(&logs, new, list);
  592     numLogs++;
  593 
  594     return new;
  595 }
  596 
  597 static void removeLogInfo(struct logInfo *log)
  598 {
  599     if (log == NULL)
  600         return;
  601 
  602     freeLogInfo(log);
  603     TAILQ_REMOVE(&logs, log, list);
  604     free(log);
  605     numLogs--;
  606 }
  607 
  608 static void freeTailLogs(int num)
  609 {
  610     message(MESS_DEBUG, "removing last %d log configs\n", num);
  611 
  612     while (num--)
  613         removeLogInfo(TAILQ_LAST(&logs, logInfoHead));
  614 
  615 }
  616 
  617 static const char *crit_to_string(enum criterium crit)
  618 {
  619     switch (crit) {
  620         case ROT_HOURLY:    return "hourly";
  621         case ROT_DAYS:      return "daily";
  622         case ROT_WEEKLY:    return "weekly";
  623         case ROT_MONTHLY:   return "monthly";
  624         case ROT_YEARLY:    return "yearly";
  625         case ROT_SIZE:      return "size";
  626         default:            return "XXX";
  627     }
  628 }
  629 
  630 static void set_criterium(enum criterium *pDst, enum criterium src, int *pSet)
  631 {
  632     if (*pSet && (*pDst != src)) {
  633         /* we are overriding a previously set criterium */
  634         message(MESS_VERBOSE, "warning: '%s' overrides previously specified '%s'\n",
  635                 crit_to_string(src), crit_to_string(*pDst));
  636     }
  637     *pDst = src;
  638     *pSet = 1;
  639 }
  640 
  641 static int readConfigPath(const char *path, struct logInfo *defConfig)
  642 {
  643     struct stat sb;
  644     int result = 0;
  645     struct logInfo defConfigBackup;
  646 
  647     if (stat(path, &sb)) {
  648         message(MESS_ERROR, "cannot stat %s: %s\n", path, strerror(errno));
  649         return 1;
  650     }
  651 
  652     if (S_ISDIR(sb.st_mode)) {
  653         char **namelist = NULL;
  654         struct dirent *dp;
  655         int here;
  656         unsigned files_count = 0, i;
  657         DIR *dirp;
  658 
  659         if ((here = open(".", O_RDONLY)) == -1) {
  660             message(MESS_ERROR, "cannot open current directory: %s\n",
  661                     strerror(errno));
  662             return 1;
  663         }
  664 
  665         if ((dirp = opendir(path)) == NULL) {
  666             message(MESS_ERROR, "cannot open directory %s: %s\n", path,
  667                     strerror(errno));
  668             close(here);
  669             return 1;
  670         }
  671         while ((dp = readdir(dirp)) != NULL) {
  672             if (checkFile(dp->d_name)) {
  673                 /* Realloc memory for namelist array if necessary */
  674                 if (files_count % REALLOC_STEP == 0) {
  675                     char **p = (char **) realloc(namelist,
  676                             (files_count +
  677                              REALLOC_STEP) * sizeof(char *));
  678                     if (p) {
  679                         namelist = p;
  680                         memset(namelist + files_count, '\0',
  681                                REALLOC_STEP * sizeof(char *));
  682                     } else {
  683                         free_2d_array(namelist, files_count);
  684                         closedir(dirp);
  685                         close(here);
  686                         message_OOM();
  687                         return 1;
  688                     }
  689                 }
  690                 /* Alloc memory for file name */
  691                 namelist[files_count] = strdup(dp->d_name);
  692                 if (namelist[files_count] != NULL) {
  693                     files_count++;
  694                 } else {
  695                     free_2d_array(namelist, files_count);
  696                     closedir(dirp);
  697                     close(here);
  698                     message_OOM();
  699                     return 1;
  700                 }
  701             }
  702         }
  703         closedir(dirp);
  704 
  705         if (files_count > 0) {
  706             qsort(namelist, files_count, sizeof(char *), compar);
  707         } else {
  708             close(here);
  709             return 0;
  710         }
  711 
  712         if (chdir(path)) {
  713             message(MESS_ERROR, "error in chdir(\"%s\"): %s\n", path,
  714                     strerror(errno));
  715             close(here);
  716             free_2d_array(namelist, files_count);
  717             return 1;
  718         }
  719 
  720         for (i = 0; i < files_count; ++i) {
  721             assert(namelist[i] != NULL);
  722             if (copyLogInfo(&defConfigBackup, defConfig)) {
  723                 freeLogInfo(&defConfigBackup);
  724                 close(here);
  725                 free_2d_array(namelist, files_count);
  726                 return 1;
  727             }
  728             if (readConfigFile(namelist[i], defConfig)) {
  729                 message(MESS_ERROR, "found error in file %s, skipping\n", namelist[i]);
  730                 freeLogInfo(defConfig);
  731                 if (copyLogInfo(defConfig, &defConfigBackup)){} /* do not check, we are already in a error path */
  732                 freeLogInfo(&defConfigBackup);
  733                 result = 1;
  734                 continue;
  735             }
  736             freeLogInfo(&defConfigBackup);
  737         }
  738 
  739         if (fchdir(here) < 0) {
  740             message(MESS_ERROR, "could not change directory to '.': %s\n", strerror(errno));
  741         }
  742         close(here);
  743         free_2d_array(namelist, files_count);
  744     } else {
  745         if (copyLogInfo(&defConfigBackup, defConfig)) {
  746             freeLogInfo(&defConfigBackup);
  747             return 1;
  748         }
  749 
  750         if (readConfigFile(path, defConfig)) {
  751             freeLogInfo(defConfig);
  752             if (copyLogInfo(defConfig, &defConfigBackup)){} /* do not check, we are already in a error path */
  753             result = 1;
  754         }
  755         freeLogInfo(&defConfigBackup);
  756     }
  757 
  758     return result;
  759 }
  760 
  761 int readAllConfigPaths(const char **paths)
  762 {
  763     int result = 0;
  764     unsigned i;
  765     const char **file;
  766     struct logInfo defConfig = {
  767         .pattern = NULL,
  768         .files = NULL,
  769         .numFiles = 0,
  770         .oldDir = NULL,
  771         .criterium = ROT_SIZE,
  772         .threshold = 1024 * 1024,
  773         .minsize = 0,
  774         .maxsize = 0,
  775         .rotateCount = 0,
  776         .rotateMinAge = 0,
  777         .rotateAge = 0,
  778         .logStart = -1,
  779         .pre = NULL,
  780         .post = NULL,
  781         .first = NULL,
  782         .last = NULL,
  783         .preremove = NULL,
  784         .logAddress = NULL,
  785         .extension = NULL,
  786         .addextension = NULL,
  787         .compress_prog = NULL,
  788         .uncompress_prog = NULL,
  789         .compress_ext = NULL,
  790         .dateformat = NULL,
  791         .flags = LOG_FLAG_IFEMPTY,
  792         .shred_cycles = 0,
  793         .createMode = NO_MODE,
  794         .createUid = NO_UID,
  795         .createGid = NO_GID,
  796         .olddirMode = NO_MODE,
  797         .olddirUid = NO_UID,
  798         .olddirGid = NO_GID,
  799         .suUid = NO_UID,
  800         .suGid = NO_GID,
  801         .compress_options_list = NULL,
  802         .compress_options_count = 0
  803     };
  804 
  805     tabooPatterns = malloc(sizeof(*tabooPatterns) * defTabooCount);
  806     if (tabooPatterns == NULL) {
  807         message_OOM();
  808         return 1;
  809     }
  810 
  811 
  812     for (i = 0; i < defTabooCount; i++) {
  813         char *pattern = NULL;
  814 
  815         /* generate a pattern by concatenating star (wildcard) to the
  816          * suffix literal
  817          */
  818         if (asprintf(&pattern, "*%s", defTabooExts[i]) < 0) {
  819             free_2d_array(tabooPatterns, tabooCount);
  820             message_OOM();
  821             return 1;
  822         }
  823 
  824         tabooPatterns[i] = pattern;
  825         tabooCount++;
  826     }
  827 
  828     for (file = paths; *file; file++) {
  829         if (readConfigPath(*file, &defConfig))
  830             result = 1;
  831     }
  832     free_2d_array(tabooPatterns, tabooCount);
  833     freeLogInfo(&defConfig);
  834     return result;
  835 }
  836 
  837 static char* parseGlobString(const char *configFile, int lineNum,
  838                              const char *buf, size_t length, char **ppos)
  839 {
  840     /* output buffer */
  841     char *globString = NULL;
  842     size_t globStringPos = 0;
  843     size_t globStringAlloc = 0;
  844     enum {
  845         PGS_INIT,   /* picking blanks, looking for '#' */
  846         PGS_DATA,   /* picking data, looking for end of line */
  847         PGS_COMMENT /* skipping comment, looking for end of line */
  848     } state = PGS_INIT;
  849 
  850     /* move the cursor at caller's side while going through the input */
  851     for (; ((size_t)(*ppos - buf) < length) && **ppos; (*ppos)++) {
  852         /* state transition (see above) */
  853         switch (state) {
  854             case PGS_INIT:
  855                 if ('#' == **ppos)
  856                     state = PGS_COMMENT;
  857                 else if (!isspace((unsigned char) **ppos))
  858                     state = PGS_DATA;
  859                 break;
  860 
  861             default:
  862                 if ('\n' == **ppos)
  863                     state = PGS_INIT;
  864         }
  865 
  866         if (PGS_COMMENT == state)
  867             /* skip comment */
  868             continue;
  869 
  870         switch (**ppos) {
  871             case '}':
  872                 message(MESS_ERROR, "%s:%d unexpected } (missing previous '{')\n", configFile, lineNum);
  873                 free(globString);
  874                 return NULL;
  875 
  876             case '{':
  877                 /* NUL-terminate globString */
  878                 assert(globStringPos < globStringAlloc);
  879                 globString[globStringPos] = '\0';
  880                 return globString;
  881 
  882             default:
  883                 break;
  884         }
  885 
  886         /* grow the output buffer if needed */
  887         if (globStringPos + 2 > globStringAlloc) {
  888             char *ptr;
  889             globStringAlloc += GLOB_STR_REALLOC_STEP;
  890             ptr = realloc(globString, globStringAlloc);
  891             if (!ptr) {
  892                 message_OOM();
  893                 free(globString);
  894                 return NULL;
  895             }
  896             globString = ptr;
  897         }
  898 
  899         /* copy a single character */
  900         globString[globStringPos++] = **ppos;
  901     }
  902 
  903     /* premature end of input */
  904     message(MESS_ERROR, "%s:%d missing '{' after log files definition\n", configFile, lineNum);
  905     free(globString);
  906     return NULL;
  907 }
  908 
  909 static int globerr(const char *pathname, int theerr)
  910 {
  911     (void) pathname;
  912 
  913     /* prevent glob() from being aborted in certain cases */
  914     switch (theerr) {
  915         case ENOTDIR:
  916             /* non-directory where directory was expected by the glob */
  917             return 0;
  918 
  919         case ENOENT:
  920             /* most likely symlink with non-existent target */
  921             return 0;
  922 
  923         default:
  924             break;
  925     }
  926 
  927     glob_errno = theerr;
  928 
  929     /* We want the glob operation to abort on error, so return 1 */
  930     return 1;
  931 }
  932 
  933 #define freeLogItem(what) \
  934     do { \
  935         free(newlog->what); \
  936         newlog->what = NULL; \
  937     } while (0)
  938 #define RAISE_ERROR() \
  939     do { \
  940         if (newlog != defConfig) { \
  941             state = STATE_ERROR; \
  942             goto next_state; \
  943         } else { \
  944             goto error; \
  945         } \
  946     } while(0)
  947 #define MAX_NESTING 16U
  948 
  949 static int readConfigFile(const char *configFile, struct logInfo *defConfig)
  950 {
  951     int fd;
  952     char *buf, *key = NULL;
  953     size_t length;
  954     int lineNum = 1;
  955     char *scriptStart = NULL;
  956     char **scriptDest = NULL;
  957     struct logInfo *newlog = defConfig;
  958     char *start, *chptr;
  959     struct stat sb;
  960     int state = STATE_DEFAULT;
  961     int logerror = 0;
  962     /* to check if incompatible criteria are specified */
  963     int criterium_set = 0;
  964     static unsigned recursion_depth = 0U;
  965     char *globerr_msg = NULL;
  966     int in_config = 0;
  967 #ifdef HAVE_MADVISE
  968     int r;
  969 #endif
  970     struct flock fd_lock = {
  971         .l_start = 0,
  972         .l_len = 0,
  973         .l_whence = SEEK_SET,
  974         .l_type = F_RDLCK
  975     };
  976 
  977     fd = open(configFile, O_RDONLY);
  978     if (fd < 0) {
  979         message(MESS_ERROR, "failed to open config file %s: %s\n",
  980                 configFile, strerror(errno));
  981         return 1;
  982     }
  983     /* We don't want anybody to change the file while we parse it,
  984      * let's try to lock it for reading. */
  985     if (fcntl(fd, F_SETLK, &fd_lock) == -1) {
  986         message(MESS_ERROR, "Could not lock file %s for reading\n",
  987                 configFile);
  988     }
  989     if (fstat(fd, &sb)) {
  990         message(MESS_ERROR, "fstat of %s failed: %s\n", configFile,
  991                 strerror(errno));
  992         close(fd);
  993         return 1;
  994     }
  995     if (!S_ISREG(sb.st_mode)) {
  996         message(MESS_DEBUG,
  997                 "Ignoring %s because it's not a regular file.\n",
  998                 configFile);
  999         close(fd);
 1000         return 0;
 1001     }
 1002 
 1003     if (!getpwuid(getuid())) {
 1004         message(MESS_ERROR, "Cannot find logrotate UID (%d) in passwd file: %s\n",
 1005                 getuid(), strerror(errno));
 1006         close(fd);
 1007         return 1;
 1008     }
 1009 
 1010     if (getuid() == ROOT_UID) {
 1011         if ((sb.st_mode & 07533) != 0400) {
 1012             message(MESS_NORMAL,
 1013                     "Potentially dangerous mode on %s: 0%o\n",
 1014                     configFile, (unsigned) (sb.st_mode & 07777));
 1015         }
 1016 
 1017         if (sb.st_mode & 0022) {
 1018             message(MESS_ERROR,
 1019                     "Ignoring %s because it is writable by group or others.\n",
 1020                     configFile);
 1021             close(fd);
 1022             return 0;
 1023         }
 1024 
 1025         if (sb.st_uid != ROOT_UID) {
 1026             message(MESS_ERROR,
 1027                     "Ignoring %s because the file owner is wrong (should be root or user with uid 0).\n",
 1028                     configFile);
 1029             close(fd);
 1030             return 0;
 1031         }
 1032     }
 1033 
 1034     length = (size_t)sb.st_size;
 1035 
 1036     if (length > 0xffffff) {
 1037         message(MESS_ERROR, "file %s too large, probably not a config file.\n",
 1038                 configFile);
 1039         close(fd);
 1040         return 1;
 1041     }
 1042 
 1043     /* We can't mmap empty file... */
 1044     if (length == 0) {
 1045         message(MESS_DEBUG,
 1046                 "Ignoring %s because it's empty.\n",
 1047                 configFile);
 1048         close(fd);
 1049         return 0;
 1050     }
 1051 
 1052 #ifdef MAP_POPULATE
 1053     buf = mmap(NULL, length, PROT_READ,
 1054             MAP_PRIVATE | MAP_POPULATE, fd, (off_t) 0);
 1055 #else /* MAP_POPULATE */
 1056     buf = mmap(NULL, length, PROT_READ,
 1057             MAP_PRIVATE, fd, (off_t) 0);
 1058 #endif /* MAP_POPULATE */
 1059 
 1060     if (buf == MAP_FAILED) {
 1061         message(MESS_ERROR, "Error mapping config file %s: %s\n",
 1062                 configFile, strerror(errno));
 1063         close(fd);
 1064         return 1;
 1065     }
 1066 
 1067 #ifdef HAVE_MADVISE
 1068 #ifdef MADV_DONTFORK
 1069     r = madvise(buf, length, MADV_SEQUENTIAL | MADV_WILLNEED | MADV_DONTFORK);
 1070 #else /* MADV_DONTFORK */
 1071     r = madvise(buf, length, MADV_SEQUENTIAL | MADV_WILLNEED);
 1072 #endif /* MADV_DONTFORK */
 1073     if (r < 0) {
 1074         message(MESS_DEBUG, "Failed to advise use of memory: %s\n",
 1075                 strerror(errno));
 1076     }
 1077 #endif /* HAVE_MADVISE */
 1078 
 1079     message(MESS_DEBUG, "reading config file %s\n", configFile);
 1080 
 1081     for (start = buf; (size_t)(start - buf) < length; start++) {
 1082         switch (state) {
 1083             case STATE_DEFAULT:
 1084                 if (isblank((unsigned char)*start))
 1085                     continue;
 1086                 /* Skip comment */
 1087                 if (*start == '#') {
 1088                     state = STATE_SKIP_LINE;
 1089                     continue;
 1090                 }
 1091 
 1092                 if (isalpha((unsigned char)*start)) {
 1093                     free(key);
 1094                     key = isolateWord(&start, &buf, length);
 1095                     if (key == NULL) {
 1096                         message(MESS_ERROR, "%s:%d failed to parse keyword\n",
 1097                                 configFile, lineNum);
 1098                         continue;
 1099                     }
 1100                     if (!isspace((unsigned char)*start)) {
 1101                         message(MESS_NORMAL, "%s:%d keyword '%s' not properly"
 1102                                 " separated, found %#x\n",
 1103                                 configFile, lineNum, key, *start);
 1104                     }
 1105                     if (!strcmp(key, "compress")) {
 1106                         newlog->flags |= LOG_FLAG_COMPRESS;
 1107                     } else if (!strcmp(key, "nocompress")) {
 1108                         newlog->flags &= ~LOG_FLAG_COMPRESS;
 1109                     } else if (!strcmp(key, "delaycompress")) {
 1110                         newlog->flags |= LOG_FLAG_DELAYCOMPRESS;
 1111                     } else if (!strcmp(key, "nodelaycompress")) {
 1112                         newlog->flags &= ~LOG_FLAG_DELAYCOMPRESS;
 1113                     } else if (!strcmp(key, "shred")) {
 1114                         newlog->flags |= LOG_FLAG_SHRED;
 1115                     } else if (!strcmp(key, "noshred")) {
 1116                         newlog->flags &= ~LOG_FLAG_SHRED;
 1117                     } else if (!strcmp(key, "sharedscripts")) {
 1118                         newlog->flags |= LOG_FLAG_SHAREDSCRIPTS;
 1119                     } else if (!strcmp(key, "nosharedscripts")) {
 1120                         newlog->flags &= ~LOG_FLAG_SHAREDSCRIPTS;
 1121                     } else if (!strcmp(key, "copytruncate")) {
 1122                         newlog->flags |= LOG_FLAG_COPYTRUNCATE;
 1123                         newlog->flags &= ~LOG_FLAG_TMPFILENAME;
 1124                     } else if (!strcmp(key, "nocopytruncate")) {
 1125                         newlog->flags &= ~LOG_FLAG_COPYTRUNCATE;
 1126                     } else if (!strcmp(key, "renamecopy")) {
 1127                         newlog->flags |= LOG_FLAG_TMPFILENAME;
 1128                         newlog->flags &= ~LOG_FLAG_COPYTRUNCATE;
 1129                     } else if (!strcmp(key, "norenamecopy")) {
 1130                         newlog->flags &= ~LOG_FLAG_TMPFILENAME;
 1131                     } else if (!strcmp(key, "copy")) {
 1132                         newlog->flags |= LOG_FLAG_COPY;
 1133                     } else if (!strcmp(key, "nocopy")) {
 1134                         newlog->flags &= ~LOG_FLAG_COPY;
 1135                     } else if (!strcmp(key, "ifempty")) {
 1136                         newlog->flags |= LOG_FLAG_IFEMPTY;
 1137                     } else if (!strcmp(key, "notifempty")) {
 1138                         newlog->flags &= ~LOG_FLAG_IFEMPTY;
 1139                     } else if (!strcmp(key, "dateext")) {
 1140                         newlog->flags |= LOG_FLAG_DATEEXT;
 1141                     } else if (!strcmp(key, "nodateext")) {
 1142                         newlog->flags &= ~LOG_FLAG_DATEEXT;
 1143                     } else if (!strcmp(key, "dateyesterday")) {
 1144                         newlog->flags |= LOG_FLAG_DATEYESTERDAY;
 1145                     } else if (!strcmp(key, "datehourago")) {
 1146                         newlog->flags |= LOG_FLAG_DATEHOURAGO;
 1147                     } else if (!strcmp(key, "dateformat")) {
 1148                         freeLogItem(dateformat);
 1149                         newlog->dateformat = isolateValue(configFile, lineNum,
 1150                                                           key, &start, &buf,
 1151                                                           length);
 1152                     } else if (!strcmp(key, "noolddir")) {
 1153                         freeLogItem(oldDir);
 1154                     } else if (!strcmp(key, "mailfirst")) {
 1155                         newlog->flags |= LOG_FLAG_MAILFIRST;
 1156                     } else if (!strcmp(key, "maillast")) {
 1157                         newlog->flags &= ~LOG_FLAG_MAILFIRST;
 1158                     } else if (!strcmp(key, "su")) {
 1159                         int rv;
 1160                         mode_t tmp_mode = NO_MODE;
 1161                         free(key);
 1162                         key = isolateLine(&start, &buf, length);
 1163                         if (key == NULL) {
 1164                             message(MESS_ERROR, "%s:%d failed to parse su option value\n",
 1165                                     configFile, lineNum);
 1166                             RAISE_ERROR();
 1167                         }
 1168 
 1169                         rv = readModeUidGid(configFile, lineNum, key, "su",
 1170                                             &tmp_mode, &newlog->suUid,
 1171                                             &newlog->suGid);
 1172                         if (rv == -1) {
 1173                             RAISE_ERROR();
 1174                         }
 1175                         else if (tmp_mode != NO_MODE) {
 1176                             message(MESS_ERROR, "%s:%d extra arguments for "
 1177                                     "su\n", configFile, lineNum);
 1178                             RAISE_ERROR();
 1179                         }
 1180                         else if (newlog->suUid == NO_UID) {
 1181                             message(MESS_ERROR, "%s:%d no user for "
 1182                                     "su\n", configFile, lineNum);
 1183                             RAISE_ERROR();
 1184                         }
 1185                         else if (newlog->suGid == NO_GID) {
 1186                             message(MESS_ERROR, "%s:%d no group for "
 1187                                     "su\n", configFile, lineNum);
 1188                             RAISE_ERROR();
 1189                         }
 1190 
 1191                         newlog->flags |= LOG_FLAG_SU;
 1192                     } else if (!strcmp(key, "create")) {
 1193                         int rv;
 1194 
 1195                         free(key);
 1196                         key = isolateLine(&start, &buf, length);
 1197                         if (key == NULL)
 1198                             continue;
 1199 
 1200                         rv = readModeUidGid(configFile, lineNum, key, "create",
 1201                                             &newlog->createMode, &newlog->createUid,
 1202                                             &newlog->createGid);
 1203                         if (rv == -1) {
 1204                             RAISE_ERROR();
 1205                         }
 1206 
 1207                         newlog->flags |= LOG_FLAG_CREATE;
 1208                     } else if (!strcmp(key, "createolddir")) {
 1209                         int rv;
 1210 
 1211                         free(key);
 1212                         key = isolateLine(&start, &buf, length);
 1213                         if (key == NULL)
 1214                             continue;
 1215 
 1216                         rv = readModeUidGid(configFile, lineNum, key, "createolddir",
 1217                                             &newlog->olddirMode, &newlog->olddirUid,
 1218                                             &newlog->olddirGid);
 1219                         if (rv == -1) {
 1220                             RAISE_ERROR();
 1221                         }
 1222 
 1223                         newlog->flags |= LOG_FLAG_OLDDIRCREATE;
 1224                     } else if (!strcmp(key, "nocreateolddir")) {
 1225                         newlog->flags &= ~LOG_FLAG_OLDDIRCREATE;
 1226                     } else if (!strcmp(key, "nocreate")) {
 1227                         newlog->flags &= ~LOG_FLAG_CREATE;
 1228                     } else if (!strcmp(key, "size") || !strcmp(key, "minsize") ||
 1229                             !strcmp(key, "maxsize")) {
 1230                         char *opt = key;
 1231 
 1232                         key = isolateValue(configFile, lineNum, opt, &start, &buf, length);
 1233                         if (key && key[0]) {
 1234                             off_t size;
 1235                             unsigned long multiplier;
 1236                             const size_t l = strlen(key) - 1;
 1237                             if (key[l] == 'k' || key[l] == 'K') {
 1238                                 key[l] = '\0';
 1239                                 multiplier = 1024;
 1240                             } else if (key[l] == 'M') {
 1241                                 key[l] = '\0';
 1242                                 multiplier = 1024 * 1024;
 1243                             } else if (key[l] == 'G') {
 1244                                 key[l] = '\0';
 1245                                 multiplier = 1024 * 1024 * 1024;
 1246                             } else if (!isdigit((unsigned char)key[l])) {
 1247                                 free(opt);
 1248                                 message(MESS_ERROR, "%s:%d unknown unit '%c'\n",
 1249                                         configFile, lineNum, key[l]);
 1250                                 RAISE_ERROR();
 1251                             } else {
 1252                                 multiplier = 1;
 1253                             }
 1254 
 1255                             size = (off_t) (multiplier * strtoull(key, &chptr, 0));
 1256                             if (*chptr || size < 0) {
 1257                                 message(MESS_ERROR, "%s:%d bad size '%s'\n",
 1258                                         configFile, lineNum, key);
 1259                                 free(opt);
 1260                                 RAISE_ERROR();
 1261                             }
 1262                             if (!strncmp(opt, "size", 4)) {
 1263                                 set_criterium(&newlog->criterium, ROT_SIZE, &criterium_set);
 1264                                 newlog->threshold = size;
 1265                             } else if (!strncmp(opt, "maxsize", 7)) {
 1266                                 newlog->maxsize = size;
 1267                             } else {
 1268                                 newlog->minsize = size;
 1269                             }
 1270                             free(opt);
 1271                         }
 1272                         else {
 1273                             free(opt);
 1274                             continue;
 1275                         }
 1276                     } else if (!strcmp(key, "shredcycles")) {
 1277                         free(key);
 1278                         key = isolateValue(configFile, lineNum, "shred cycles",
 1279                                            &start, &buf, length);
 1280                         if (key == NULL) {
 1281                             RAISE_ERROR();
 1282                         }
 1283                         newlog->shred_cycles = (int)strtoul(key, &chptr, 0);
 1284                         if (*chptr || newlog->shred_cycles < 0) {
 1285                             message(MESS_ERROR, "%s:%d bad shred cycles '%s'\n",
 1286                                     configFile, lineNum, key);
 1287                             RAISE_ERROR();
 1288                         }
 1289                     } else if (!strcmp(key, "hourly")) {
 1290                         set_criterium(&newlog->criterium, ROT_HOURLY, &criterium_set);
 1291                     } else if (!strcmp(key, "daily")) {
 1292                         set_criterium(&newlog->criterium, ROT_DAYS, &criterium_set);
 1293                         newlog->threshold = 1;
 1294                     } else if (!strcmp(key, "monthly")) {
 1295                         set_criterium(&newlog->criterium, ROT_MONTHLY, &criterium_set);
 1296                     } else if (!strcmp(key, "weekly")) {
 1297                         unsigned weekday;
 1298                         char tmp;
 1299                         set_criterium(&newlog->criterium, ROT_WEEKLY, &criterium_set);
 1300                         free(key);
 1301                         key = isolateLine(&start, &buf, length);
 1302                         if (key == NULL || key[0] == '\0') {
 1303                             /* default to Sunday if no argument was given */
 1304                             newlog->weekday = 0;
 1305                             continue;
 1306                         }
 1307 
 1308                         if (1 == sscanf(key, "%u%c", &weekday, &tmp) && weekday <= 7) {
 1309                             /* use the selected weekday, 7 means "once per week" */
 1310                             newlog->weekday = weekday;
 1311                             continue;
 1312                         }
 1313                         message(MESS_ERROR, "%s:%d bad weekly directive '%s'\n",
 1314                                 configFile, lineNum, key);
 1315                         goto error;
 1316                     } else if (!strcmp(key, "yearly")) {
 1317                         set_criterium(&newlog->criterium, ROT_YEARLY, &criterium_set);
 1318                     } else if (!strcmp(key, "rotate")) {
 1319                         free(key);
 1320                         key = isolateValue(configFile, lineNum, "rotate count", &start,
 1321                                            &buf, length);
 1322                         if (key == NULL) {
 1323                             RAISE_ERROR();
 1324                         }
 1325                         newlog->rotateCount = (int)strtol(key, &chptr, 0);
 1326                         if (*chptr || newlog->rotateCount < -1) {
 1327                             message(MESS_ERROR,
 1328                                     "%s:%d bad rotation count '%s'\n",
 1329                                     configFile, lineNum, key);
 1330                             RAISE_ERROR();
 1331                         }
 1332                     } else if (!strcmp(key, "start")) {
 1333                         free(key);
 1334                         key = isolateValue(configFile, lineNum, "start count", &start,
 1335                                            &buf, length);
 1336                         if (key == NULL) {
 1337                             RAISE_ERROR();
 1338                         }
 1339                         newlog->logStart = (int)strtoul(key, &chptr, 0);
 1340                         if (*chptr || newlog->logStart < 0) {
 1341                             message(MESS_ERROR, "%s:%d bad start count '%s'\n",
 1342                                     configFile, lineNum, key);
 1343                             RAISE_ERROR();
 1344                         }
 1345                     } else if (!strcmp(key, "minage")) {
 1346                         free(key);
 1347                         key = isolateValue(configFile, lineNum, "minage count", &start,
 1348                                            &buf, length);
 1349                         if (key == NULL) {
 1350                             RAISE_ERROR();
 1351                         }
 1352                         newlog->rotateMinAge = (int)strtoul(key, &chptr, 0);
 1353                         if (*chptr || newlog->rotateMinAge < 0) {
 1354                             message(MESS_ERROR, "%s:%d bad minimum age '%s'\n",
 1355                                     configFile, lineNum, start);
 1356                             RAISE_ERROR();
 1357                         }
 1358                     } else if (!strcmp(key, "maxage")) {
 1359                         free(key);
 1360                         key = isolateValue(configFile, lineNum, "maxage count", &start,
 1361                                            &buf, length);
 1362                         if (key == NULL) {
 1363                             RAISE_ERROR();
 1364                         }
 1365                         newlog->rotateAge = (int)strtoul(key, &chptr, 0);
 1366                         if (*chptr || newlog->rotateAge < 0) {
 1367                             message(MESS_ERROR, "%s:%d bad maximum age '%s'\n",
 1368                                     configFile, lineNum, start);
 1369                             RAISE_ERROR();
 1370                         }
 1371                     } else if (!strcmp(key, "errors")) {
 1372                         message(MESS_NORMAL,
 1373                                 "%s: %d: the errors directive is deprecated and no longer used.\n",
 1374                                 configFile, lineNum);
 1375                     } else if (!strcmp(key, "mail")) {
 1376                         freeLogItem(logAddress);
 1377                         if (!(newlog->logAddress = readAddress(configFile, lineNum,
 1378                                         "mail", &start, &buf, length))) {
 1379                             RAISE_ERROR();
 1380                         }
 1381                     } else if (!strcmp(key, "nomail")) {
 1382                         freeLogItem(logAddress);
 1383                     } else if (!strcmp(key, "missingok")) {
 1384                         newlog->flags |= LOG_FLAG_MISSINGOK;
 1385                     } else if (!strcmp(key, "nomissingok")) {
 1386                         newlog->flags &= ~LOG_FLAG_MISSINGOK;
 1387                     } else if (!strcmp(key, "prerotate")) {
 1388                         freeLogItem (pre);
 1389                         scriptStart = start;
 1390                         scriptDest = &newlog->pre;
 1391                         state = STATE_LOAD_SCRIPT;
 1392                     } else if (!strcmp(key, "firstaction")) {
 1393                         freeLogItem (first);
 1394                         scriptStart = start;
 1395                         scriptDest = &newlog->first;
 1396                         state = STATE_LOAD_SCRIPT;
 1397                     } else if (!strcmp(key, "postrotate")) {
 1398                         freeLogItem (post);
 1399                         scriptStart = start;
 1400                         scriptDest = &newlog->post;
 1401                         state = STATE_LOAD_SCRIPT;
 1402                     } else if (!strcmp(key, "lastaction")) {
 1403                         freeLogItem (last);
 1404                         scriptStart = start;
 1405                         scriptDest = &newlog->last;
 1406                         state = STATE_LOAD_SCRIPT;
 1407                     } else if (!strcmp(key, "preremove")) {
 1408                         freeLogItem (preremove);
 1409                         scriptStart = start;
 1410                         scriptDest = &newlog->preremove;
 1411                         state = STATE_LOAD_SCRIPT;
 1412                     } else if (!strcmp(key, "tabooext")) {
 1413                         char *endtag;
 1414 
 1415                         if (newlog != defConfig) {
 1416                             message(MESS_ERROR,
 1417                                     "%s:%d tabooext may not appear inside "
 1418                                     "of log file definition\n", configFile,
 1419                                     lineNum);
 1420                             state = STATE_ERROR;
 1421                             continue;
 1422                         }
 1423                         free(key);
 1424                         key = isolateValue(configFile, lineNum, "tabooext", &start,
 1425                                            &buf, length);
 1426                         if (key == NULL)
 1427                             continue;
 1428                         endtag = key;
 1429                         if (*endtag == '+') {
 1430                             endtag++;
 1431                             while (isspace((unsigned char)*endtag) && *endtag)
 1432                                 endtag++;
 1433                         } else {
 1434                             free_2d_array(tabooPatterns, tabooCount);
 1435                             tabooCount = 0;
 1436                             /* realloc of NULL is safe by definition */
 1437                             tabooPatterns = NULL;
 1438                         }
 1439 
 1440                         while (*endtag) {
 1441                             char *pattern = NULL;
 1442 
 1443                             chptr = endtag;
 1444                             while (!isspace((unsigned char)*chptr) && *chptr != ',' && *chptr)
 1445                                 chptr++;
 1446 
 1447                             /* accept only non-empty patterns to avoid exclusion of everything */
 1448                             if (endtag < chptr) {
 1449                                 char **tmp = realloc(tabooPatterns, sizeof(*tabooPatterns) *
 1450                                         (tabooCount + 1));
 1451                                 if (tmp == NULL) {
 1452                                     message_OOM();
 1453                                     RAISE_ERROR();
 1454                                 }
 1455                                 tabooPatterns = tmp;
 1456                                 if (asprintf(&pattern, "*%.*s", (int)(chptr - endtag), endtag) < 0) {
 1457                                     message_OOM();
 1458                                     RAISE_ERROR();
 1459                                 }
 1460 
 1461                                 tabooPatterns[tabooCount] = pattern;
 1462                                 tabooCount++;
 1463                             }
 1464 
 1465                             endtag = chptr;
 1466                             if (*endtag == ',')
 1467                                 endtag++;
 1468                             while (*endtag && isspace((unsigned char)*endtag))
 1469                                 endtag++;
 1470                         }
 1471                     } else if (!strcmp(key, "taboopat")) {
 1472                         char *endtag;
 1473 
 1474                         if (newlog != defConfig) {
 1475                             message(MESS_ERROR,
 1476                                     "%s:%d taboopat may not appear inside "
 1477                                     "of log file definition\n", configFile,
 1478                                     lineNum);
 1479                             state = STATE_ERROR;
 1480                             continue;
 1481                         }
 1482                         free(key);
 1483                         key = isolateValue(configFile, lineNum, "taboopat", &start,
 1484                                            &buf, length);
 1485                         if (key == NULL)
 1486                             continue;
 1487 
 1488                         endtag = key;
 1489                         if (*endtag == '+') {
 1490                             endtag++;
 1491                             while (isspace((unsigned char)*endtag) && *endtag)
 1492                                 endtag++;
 1493                         } else {
 1494                             free_2d_array(tabooPatterns, tabooCount);
 1495                             tabooCount = 0;
 1496                             /* realloc of NULL is safe by definition */
 1497                             tabooPatterns = NULL;
 1498                         }
 1499 
 1500                         while (*endtag) {
 1501                             char *pattern = NULL;
 1502                             char **tmp;
 1503 
 1504                             chptr = endtag;
 1505                             while (!isspace((unsigned char)*chptr) && *chptr != ',' && *chptr)
 1506                                 chptr++;
 1507 
 1508                             tmp = realloc(tabooPatterns, sizeof(*tabooPatterns) *
 1509                                     (tabooCount + 1));
 1510                             if (tmp == NULL) {
 1511                                 message_OOM();
 1512                                 RAISE_ERROR();
 1513                             }
 1514                             tabooPatterns = tmp;
 1515                             if (asprintf(&pattern, "%.*s", (int)(chptr - endtag), endtag) < 0) {
 1516                                 message_OOM();
 1517                                 RAISE_ERROR();
 1518                             }
 1519 
 1520                             tabooPatterns[tabooCount] = pattern;
 1521                             tabooCount++;
 1522 
 1523                             endtag = chptr;
 1524                             if (*endtag == ',')
 1525                                 endtag++;
 1526                             while (*endtag && isspace((unsigned char)*endtag))
 1527                                 endtag++;
 1528                         }
 1529                     } else if (!strcmp(key, "include")) {
 1530                         int rv;
 1531 
 1532                         free(key);
 1533                         key = isolateValue(configFile, lineNum, "include", &start,
 1534                                            &buf, length);
 1535                         if (key == NULL) {
 1536                             RAISE_ERROR();
 1537                         }
 1538 
 1539                         if (key[0] == '~' && key[1] == '/') {
 1540                             /* replace '~' with content of $HOME cause low-level functions
 1541                              * like stat() do not support the glob ~
 1542                              */
 1543                             const char *env_home = secure_getenv("HOME");
 1544                             char *new_key = NULL;
 1545 
 1546                             if (!env_home) {
 1547                                 const struct passwd *pwd = getpwuid(getuid());
 1548                                 message(MESS_DEBUG,
 1549                                         "%s:%d cannot get HOME directory from environment "
 1550                                         "to replace ~/ in include directive\n",
 1551                                         configFile, lineNum);
 1552                                 if (!pwd) {
 1553                                     message(MESS_ERROR, "%s:%d cannot get passwd entry for "
 1554                                             "running user %u: %s\n",
 1555                                            configFile, lineNum, getuid(), strerror(errno));
 1556                                     RAISE_ERROR();
 1557                                 }
 1558                                 env_home = pwd->pw_dir;
 1559                             }
 1560 
 1561                             if (asprintf(&new_key, "%s/%s", env_home, key + 2) < 0) {
 1562                                 message_OOM();
 1563                                 RAISE_ERROR();
 1564                             }
 1565                             message(MESS_DEBUG, "%s:%d replaced %s with '%s' for include directive\n",
 1566                                     configFile, lineNum, key, env_home);
 1567                             free(key);
 1568                             key = new_key;
 1569                         }
 1570 
 1571                         message(MESS_DEBUG, "including %s\n", key);
 1572                         if (recursion_depth >= MAX_NESTING) {
 1573                             message(MESS_ERROR, "%s:%d include nesting too deep\n",
 1574                                     configFile, lineNum);
 1575                             logerror = 1;
 1576                             continue;
 1577                         }
 1578 
 1579                         ++recursion_depth;
 1580                         rv = readConfigPath(key, newlog);
 1581                         --recursion_depth;
 1582 
 1583                         if (rv) {
 1584                             logerror = 1;
 1585                             continue;
 1586                         }
 1587                     } else if (!strcmp(key, "olddir")) {
 1588                         freeLogItem (oldDir);
 1589 
 1590                         if (!(newlog->oldDir = readPath(configFile, lineNum,
 1591                                         "olddir", &start, &buf, length))) {
 1592                             RAISE_ERROR();
 1593                         }
 1594                         message(MESS_DEBUG, "olddir is now %s\n", newlog->oldDir);
 1595                     } else if (!strcmp(key, "extension")) {
 1596                         free(key);
 1597                         key = isolateValue(configFile, lineNum, "extension name", &start,
 1598                                            &buf, length);
 1599                         if (key == NULL) {
 1600                             RAISE_ERROR();
 1601                         }
 1602                         freeLogItem (extension);
 1603                         newlog->extension = key;
 1604                         key = NULL;
 1605                         message(MESS_DEBUG, "extension is now %s\n", newlog->extension);
 1606 
 1607                     } else if (!strcmp(key, "addextension")) {
 1608                         free(key);
 1609                         key = isolateValue(configFile, lineNum, "addextension name", &start,
 1610                                            &buf, length);
 1611                         if (key == NULL) {
 1612                             RAISE_ERROR();
 1613                         }
 1614                         freeLogItem (addextension);
 1615                         newlog->addextension = key;
 1616                         key = NULL;
 1617                         message(MESS_DEBUG, "addextension is now %s\n",
 1618                                 newlog->addextension);
 1619 
 1620                     } else if (!strcmp(key, "compresscmd")) {
 1621                         char *compresscmd_full;
 1622                         const char *compresscmd_base;
 1623                         unsigned i;
 1624 
 1625                         freeLogItem (compress_prog);
 1626 
 1627                         if (!
 1628                                 (newlog->compress_prog =
 1629                                  readPath(configFile, lineNum, "compress", &start, &buf, length))) {
 1630                             RAISE_ERROR();
 1631                         }
 1632 
 1633                         message(MESS_DEBUG, "compress_prog is now %s\n",
 1634                                 newlog->compress_prog);
 1635 
 1636                         compresscmd_full = strdup(newlog->compress_prog);
 1637                         if (compresscmd_full == NULL) {
 1638                             message_OOM();
 1639                             RAISE_ERROR();
 1640                         }
 1641 
 1642                         compresscmd_base = basename(compresscmd_full);
 1643 
 1644                         /* we check whether we changed the compress_cmd. In case we use the appropriate extension
 1645                            as listed in compress_cmd_list */
 1646                         for(i = 0; i < compress_cmd_list_size; i++) {
 1647                             if (!strcmp(compress_cmd_list[i].cmd, compresscmd_base)) {
 1648                                 freeLogItem (compress_ext);
 1649                                 newlog->compress_ext = strdup(compress_cmd_list[i].ext);
 1650                                 if (newlog->compress_ext == NULL) {
 1651                                     message_OOM();
 1652                                     free(compresscmd_full);
 1653                                     RAISE_ERROR();
 1654                                 }
 1655                                 message(MESS_DEBUG, "compress_ext was changed to %s\n", newlog->compress_ext);
 1656                                 break;
 1657                             }
 1658                         }
 1659                         free(compresscmd_full);
 1660                     } else if (!strcmp(key, "uncompresscmd")) {
 1661                         freeLogItem (uncompress_prog);
 1662 
 1663                         if (!
 1664                                 (newlog->uncompress_prog =
 1665                                  readPath(configFile, lineNum, "uncompress",
 1666                                           &start, &buf, length))) {
 1667                             RAISE_ERROR();
 1668                         }
 1669 
 1670                         message(MESS_DEBUG, "uncompress_prog is now %s\n",
 1671                                 newlog->uncompress_prog);
 1672 
 1673                     } else if (!strcmp(key, "compressoptions")) {
 1674                         char *options;
 1675 
 1676                         if (newlog->compress_options_list) {
 1677                             free(newlog->compress_options_list);
 1678                             newlog->compress_options_list = NULL;
 1679                             newlog->compress_options_count = 0;
 1680                         }
 1681 
 1682                         if (!(options = isolateLine(&start, &buf, length))) {
 1683                             RAISE_ERROR();
 1684                         }
 1685 
 1686                         if (poptParseArgvString(options,
 1687                                                 &newlog->compress_options_count,
 1688                                                 &newlog->compress_options_list)) {
 1689                             message(MESS_ERROR,
 1690                                     "%s:%d invalid compression options\n",
 1691                                     configFile, lineNum);
 1692                             free(options);
 1693                             RAISE_ERROR();
 1694                         }
 1695 
 1696                         message(MESS_DEBUG, "compress_options is now %s\n",
 1697                                 options);
 1698                         free(options);
 1699                     } else if (!strcmp(key, "compressext")) {
 1700                         freeLogItem (compress_ext);
 1701 
 1702                         if (!
 1703                                 (newlog->compress_ext =
 1704                                  readPath(configFile, lineNum, "compress-ext",
 1705                                           &start, &buf, length))) {
 1706                             RAISE_ERROR();
 1707                         }
 1708 
 1709                         message(MESS_DEBUG, "compress_ext is now %s\n",
 1710                                 newlog->compress_ext);
 1711                     } else {
 1712                         message(MESS_ERROR, "%s:%d unknown option '%s' "
 1713                                 "-- ignoring line\n", configFile, lineNum, key);
 1714                         if (*start != '\n')
 1715                             state = STATE_SKIP_LINE;
 1716                     }
 1717                 } else if (*start == '/' || *start == '"' || *start == '\''
 1718 #ifdef GLOB_TILDE
 1719                         || *start == '~'
 1720 #endif
 1721                         ) {
 1722                     char *glob_string;
 1723                     size_t glob_count;
 1724                     int argc, argNum;
 1725                     const char **argv;
 1726                     in_config = 0;
 1727                     if (newlog != defConfig) {
 1728                         message(MESS_ERROR, "%s:%d unexpected log filename\n",
 1729                                 configFile, lineNum);
 1730                         state = STATE_ERROR;
 1731                         continue;
 1732                     }
 1733 
 1734                     /* If no compression options were found in config file, set
 1735                        default values */
 1736                     if (!newlog->compress_prog)
 1737                         newlog->compress_prog = strdup(COMPRESS_COMMAND);
 1738                     if (!newlog->uncompress_prog)
 1739                         newlog->uncompress_prog = strdup(UNCOMPRESS_COMMAND);
 1740                     if (!newlog->compress_ext)
 1741                         newlog->compress_ext = strdup(COMPRESS_EXT);
 1742 
 1743                     if (!newlog->compress_prog || !newlog->uncompress_prog || !newlog->compress_ext) {
 1744                         message_OOM();
 1745                         goto error;
 1746                     }
 1747 
 1748                     /* Allocate a new logInfo structure and insert it into the logs
 1749                        queue, copying the actual values from defConfig */
 1750                     if ((newlog = newLogInfo(defConfig)) == NULL)
 1751                         goto error;
 1752 
 1753                     glob_string = parseGlobString(configFile, lineNum, buf, length, &start);
 1754                     if (glob_string)
 1755                         in_config = 1;
 1756                     else
 1757                         /* error already printed */
 1758                         goto error;
 1759 
 1760                     if (poptParseArgvString(glob_string, &argc, &argv)) {
 1761                         message(MESS_ERROR, "%s:%d error parsing filename\n",
 1762                                 configFile, lineNum);
 1763                         free(glob_string);
 1764                         goto error;
 1765                     } else if (argc < 1) {
 1766                         message(MESS_ERROR,
 1767                                 "%s:%d { expected after log file name(s)\n",
 1768                                 configFile, lineNum);
 1769                         free(glob_string);
 1770                         goto error;
 1771                     }
 1772 
 1773                     newlog->files = NULL;
 1774                     newlog->numFiles = 0;
 1775                     for (argNum = 0; argNum < argc; argNum++) {
 1776                         char **tmp;
 1777                         int rc;
 1778                         glob_t globResult;
 1779 
 1780                         if (globerr_msg) {
 1781                             free(globerr_msg);
 1782                             globerr_msg = NULL;
 1783                         }
 1784 
 1785                         rc = glob(argv[argNum], GLOB_NOCHECK
 1786 #ifdef GLOB_TILDE
 1787                                 | GLOB_TILDE
 1788 #endif
 1789                                 , globerr, &globResult);
 1790                         if (rc == GLOB_ABORTED) {
 1791                             if (newlog->flags & LOG_FLAG_MISSINGOK) {
 1792                                 continue;
 1793                             }
 1794 
 1795                             /* We don't yet know whether this stanza has "missingok"
 1796                              * set, so store the error message for later. */
 1797                             rc = asprintf(&globerr_msg, "%s:%d glob failed for %s: %s\n",
 1798                                           configFile, lineNum, argv[argNum], strerror(glob_errno));
 1799                             if (rc == -1)
 1800                                 globerr_msg = NULL;
 1801 
 1802                             globResult.gl_pathc = 0;
 1803                         }
 1804 
 1805                         tmp = realloc(newlog->files,
 1806                                     sizeof(*newlog->files) * (newlog->numFiles +
 1807                                         globResult.
 1808                                         gl_pathc));
 1809                         if (tmp == NULL) {
 1810                             message_OOM();
 1811                             logerror = 1;
 1812                             goto duperror;
 1813                         }
 1814 
 1815                         newlog->files = tmp;
 1816 
 1817                         for (glob_count = 0; glob_count < globResult.gl_pathc; glob_count++) {
 1818                             struct logInfo *log;
 1819 
 1820                             /* if we glob directories we can get false matches */
 1821                             if (!lstat(globResult.gl_pathv[glob_count], &sb) &&
 1822                                     S_ISDIR(sb.st_mode)) {
 1823                                 continue;
 1824                             }
 1825 
 1826                             for (log = logs.tqh_first; log != NULL;
 1827                                     log = log->list.tqe_next) {
 1828                                 unsigned k;
 1829                                 for (k = 0; k < log->numFiles; k++) {
 1830                                     if (!strcmp(log->files[k],
 1831                                                 globResult.gl_pathv[glob_count])) {
 1832                                         message(MESS_ERROR,
 1833                                                 "%s:%d duplicate log entry for %s\n",
 1834                                                 configFile, lineNum,
 1835                                                 globResult.gl_pathv[glob_count]);
 1836                                         logerror = 1;
 1837                                         goto duperror;
 1838                                     }
 1839                                 }
 1840                             }
 1841 
 1842                             newlog->files[newlog->numFiles] =
 1843                                 strdup(globResult.gl_pathv[glob_count]);
 1844                             if (newlog->files[newlog->numFiles] == NULL) {
 1845                                 message_OOM();
 1846                                 logerror = 1;
 1847                                 goto duperror;
 1848                             }
 1849                             newlog->numFiles++;
 1850                         }
 1851 duperror:
 1852                         globfree(&globResult);
 1853                     }
 1854 
 1855                     newlog->pattern = glob_string;
 1856 
 1857                     free(argv);
 1858 
 1859                 } else if (*start == '}') {
 1860                     if (newlog == defConfig) {
 1861                         message(MESS_ERROR, "%s:%d unexpected }\n", configFile,
 1862                                 lineNum);
 1863                         goto error;
 1864                     }
 1865                     if (!in_config) {
 1866                         message(MESS_ERROR, "%s:%d unexpected } (missing previous '{')\n", configFile,
 1867                                 lineNum);
 1868                         goto error;
 1869                     }
 1870                     in_config = 0;
 1871                     if (globerr_msg) {
 1872                         if (!(newlog->flags & LOG_FLAG_MISSINGOK))
 1873                             message(MESS_ERROR, "%s", globerr_msg);
 1874                         free(globerr_msg);
 1875                         globerr_msg = NULL;
 1876                         if (!(newlog->flags & LOG_FLAG_MISSINGOK))
 1877                             goto error;
 1878                     }
 1879 
 1880                     if (newlog->oldDir) {
 1881                         unsigned j;
 1882                         for (j = 0; j < newlog->numFiles; j++) {
 1883                             char *ld;
 1884                             char *dirpath;
 1885                             const char *dirName;
 1886                             struct stat sb2;
 1887 
 1888                             dirpath = strdup(newlog->files[j]);
 1889                             if (dirpath == NULL) {
 1890                                 message_OOM();
 1891                                 goto error;
 1892                             }
 1893 
 1894                             dirName = dirname(dirpath);
 1895                             if (stat(dirName, &sb2)) {
 1896                                 if (!(newlog->flags & LOG_FLAG_MISSINGOK)) {
 1897                                     message(MESS_ERROR,
 1898                                             "%s:%d error verifying log file "
 1899                                             "path %s: %s\n", configFile, lineNum,
 1900                                             dirName, strerror(errno));
 1901                                     free(dirpath);
 1902                                     goto error;
 1903                                 }
 1904                                 else {
 1905                                     message(MESS_DEBUG,
 1906                                             "%s:%d verifying log file "
 1907                                             "path failed %s: %s, log is probably missing, "
 1908                                             "but missingok is set, so this is not an error.\n",
 1909                                             configFile, lineNum,
 1910                                             dirName, strerror(errno));
 1911                                     free(dirpath);
 1912                                     continue;
 1913                                 }
 1914                             }
 1915                             if (asprintf(&ld, "%s/%s", dirName, newlog->oldDir) < 0) {
 1916                                 message_OOM();
 1917                                 free(dirpath);
 1918                                 goto error;
 1919                             }
 1920 
 1921                             free(dirpath);
 1922 
 1923                             if (newlog->oldDir[0] != '/') {
 1924                                 dirName = ld;
 1925                             }
 1926                             else {
 1927                                 dirName = newlog->oldDir;
 1928                             }
 1929 
 1930                             if (stat(dirName, &sb)) {
 1931                                 if (errno == ENOENT && (newlog->flags & LOG_FLAG_OLDDIRCREATE)) {
 1932                                     int ret;
 1933                                     if (newlog->flags & LOG_FLAG_SU) {
 1934                                         if (switch_user(newlog->suUid, newlog->suGid) != 0) {
 1935                                             free(ld);
 1936                                             goto error;
 1937                                         }
 1938                                     }
 1939                                     ret = mkpath(dirName, newlog->olddirMode,
 1940                                             newlog->olddirUid, newlog->olddirGid);
 1941                                     if (newlog->flags & LOG_FLAG_SU) {
 1942                                         if (switch_user_back() != 0) {
 1943                                             free(ld);
 1944                                             goto error;
 1945                                         }
 1946                                     }
 1947                                     if (ret) {
 1948                                         free(ld);
 1949                                         goto error;
 1950                                     }
 1951                                 }
 1952                                 else {
 1953                                     message(MESS_ERROR, "%s:%d error verifying olddir "
 1954                                             "path %s: %s\n", configFile, lineNum,
 1955                                             dirName, strerror(errno));
 1956                                     free(ld);
 1957                                     goto error;
 1958                                 }
 1959                             }
 1960 
 1961                             free(ld);
 1962 
 1963                             if (sb.st_dev != sb2.st_dev
 1964                                     && !(newlog->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY | LOG_FLAG_TMPFILENAME))) {
 1965                                 message(MESS_ERROR,
 1966                                         "%s:%d olddir %s and log file %s "
 1967                                         "are on different devices\n", configFile,
 1968                                         lineNum, newlog->oldDir, newlog->files[j]);
 1969                                 goto error;
 1970                             }
 1971                         }
 1972                     }
 1973 
 1974                     criterium_set = 0;
 1975                     newlog = defConfig;
 1976                     state = STATE_DEFINITION_END;
 1977                 } else if (*start != '\n') {
 1978                     message(MESS_ERROR, "%s:%d lines must begin with a keyword "
 1979                             "or a filename (possibly in double quotes)\n",
 1980                             configFile, lineNum);
 1981                     state = STATE_SKIP_LINE;
 1982                 }
 1983                 break;
 1984             case STATE_SKIP_LINE:
 1985             case STATE_SKIP_LINE | STATE_SKIP_CONFIG:
 1986                 if (*start == '\n')
 1987                     state = (state & STATE_SKIP_CONFIG) ? STATE_SKIP_CONFIG : STATE_DEFAULT;
 1988                 break;
 1989             case STATE_SKIP_LINE | STATE_LOAD_SCRIPT:
 1990                 if (*start == '\n')
 1991                     state = STATE_LOAD_SCRIPT;
 1992                 break;
 1993             case STATE_SKIP_LINE | STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG:
 1994                 if (*start == '\n')
 1995                     state = STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG;
 1996                 break;
 1997             case STATE_DEFINITION_END:
 1998             case STATE_DEFINITION_END | STATE_SKIP_CONFIG:
 1999                 if (isblank((unsigned char)*start))
 2000                     continue;
 2001                 if (*start != '\n') {
 2002                     message(MESS_ERROR, "%s:%d, unexpected text after }\n",
 2003                             configFile, lineNum);
 2004                     state = STATE_SKIP_LINE | ((state & STATE_SKIP_CONFIG) ? STATE_SKIP_CONFIG : 0);
 2005                 }
 2006                 else
 2007                     state = (state & STATE_SKIP_CONFIG) ? STATE_SKIP_CONFIG : STATE_DEFAULT;
 2008                 break;
 2009             case STATE_ERROR:
 2010                 assert(newlog != defConfig);
 2011 
 2012                 message(MESS_ERROR, "found error in %s, skipping\n",
 2013                         newlog->pattern ? newlog->pattern : "log config");
 2014 
 2015                 logerror = 1;
 2016                 state = STATE_SKIP_CONFIG;
 2017                 break;
 2018             case STATE_LOAD_SCRIPT:
 2019             case STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG:
 2020                 free(key);
 2021                 key = isolateWord(&start, &buf, length);
 2022                 if (key == NULL)
 2023                     continue;
 2024 
 2025                 if (strcmp(key, "endscript") == 0) {
 2026                     if (state & STATE_SKIP_CONFIG) {
 2027                         state = STATE_SKIP_CONFIG;
 2028                     }
 2029                     else {
 2030                         const char *endtag = start - 9;
 2031                         while (*endtag != '\n')
 2032                             endtag--;
 2033                         endtag++;
 2034                         *scriptDest = strndup(scriptStart, (size_t)(endtag - scriptStart));
 2035                         if (*scriptDest == NULL) {
 2036                             message_OOM();
 2037                             goto error;
 2038                         }
 2039 
 2040                         scriptDest = NULL;
 2041                         scriptStart = NULL;
 2042                     }
 2043                     state = (state & STATE_SKIP_CONFIG) ? STATE_SKIP_CONFIG : STATE_DEFAULT;
 2044                 }
 2045                 else {
 2046                     state = (*start == '\n' ? 0 : STATE_SKIP_LINE) |
 2047                         STATE_LOAD_SCRIPT |
 2048                         ((state & STATE_SKIP_CONFIG) ? STATE_SKIP_CONFIG : 0);
 2049                 }
 2050                 break;
 2051             case STATE_SKIP_CONFIG:
 2052                 if (*start == '}') {
 2053                     state = STATE_DEFAULT;
 2054                     freeTailLogs(1);
 2055                     newlog = defConfig;
 2056                 }
 2057                 else {
 2058                     free(key);
 2059                     key = isolateWord(&start, &buf, length);
 2060                     if (key == NULL)
 2061                         continue;
 2062                     if (
 2063                             (strcmp(key, "postrotate") == 0) ||
 2064                             (strcmp(key, "prerotate") == 0) ||
 2065                             (strcmp(key, "firstaction") == 0) ||
 2066                             (strcmp(key, "lastaction") == 0) ||
 2067                             (strcmp(key, "preremove") == 0)
 2068                             ) {
 2069                         state = STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG;
 2070                     }
 2071                     else {
 2072                         /* isolateWord moves the "start" pointer.
 2073                          * If we have a line like
 2074                          *    rotate 5
 2075                          * after isolateWord "start" points to "5" and it
 2076                          * is OK to skip the line, but if we have a line
 2077                          * like the following
 2078                          *    nocompress
 2079                          * after isolateWord "start" points to "\n". In
 2080                          * this case if we skip a line, we skip the next
 2081                          * line, not the current "nocompress" one,
 2082                          * because in the for cycle the "start"
 2083                          * pointer is increased by one and, after this,
 2084                          * "start" points to the beginning of the next line.
 2085                          */
 2086                         if (*start != '\n') {
 2087                             state = STATE_SKIP_LINE | STATE_SKIP_CONFIG;
 2088                         }
 2089                     }
 2090                 }
 2091                 break;
 2092             default:
 2093                 message(MESS_FATAL,
 2094                         "%s: %d: readConfigFile() unknown state: %#x\n",
 2095                         configFile, lineNum, state);
 2096         }
 2097         if (*start == '\n') {
 2098             lineNum++;
 2099         }
 2100 
 2101 next_state: ;
 2102     }
 2103 
 2104     if (scriptStart) {
 2105         message(MESS_ERROR,
 2106                 "%s:prerotate, postrotate or preremove without endscript\n",
 2107                 configFile);
 2108         goto error;
 2109     }
 2110 
 2111     free(key);
 2112 
 2113     munmap(buf, length);
 2114     close(fd);
 2115     free(globerr_msg);
 2116     return logerror;
 2117 error:
 2118     /* free is a NULL-safe operation */
 2119     free(key);
 2120     munmap(buf, length);
 2121     close(fd);
 2122     free(globerr_msg);
 2123     return 1;
 2124 }
 2125 
 2126 /* vim: set et sw=4 ts=4: */