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