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