#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "basenames.h" #include "log.h" #include "logrotate.h" #if !defined(GLOB_ABORTED) && defined(GLOB_ABEND) #define GLOB_ABORTED GLOB_ABEND #endif #define REALLOC_STEP 10 #if defined(SunOS) && !defined(isblank) #define isblank(c) ( (c) == ' ' || (c) == '\t' ) ? 1 : 0 #endif static char * defTabooExts[] = { ".rpmsave", ".rpmorig", "~", ",v", ".rpmnew", ".swp" }; static int defTabooCount = sizeof(defTabooExts) / sizeof(char *); /* I shouldn't use globals here :-( */ static char ** tabooExts = NULL; int tabooCount = 0; static int readConfigFile(const char * configFile, logInfo * defConfig, logInfo ** logsPtr, int * numLogsPtr); static int globerr(const char * pathname, int theerr); static struct rotatePatternElement * parsePattern(const char * pattern, const char * configFile, int lineNum); static int isolateValue(const char * fileName, int lineNum, char * key, char ** startPtr, char ** endPtr) { char * chptr = *startPtr; while (isblank(*chptr)) chptr++; if (*chptr == '=') { chptr++; while (*chptr && isblank(*chptr)) chptr++; } if (*chptr == '\n') { message(MESS_ERROR, "%s:%d argument expected after %s\n", fileName, lineNum, key); return 1; } *startPtr = chptr; while (*chptr != '\n') chptr++; while (isspace(*chptr)) chptr--; *endPtr = chptr + 1; return 0; } static char *readPath(const char *configFile, int lineNum, char *key, char **startPtr) { char oldchar; char *endtag, *chptr; char *start = *startPtr; char *path; if (!isolateValue(configFile, lineNum, key, &start, &endtag)) { oldchar = *endtag, *endtag = '\0'; chptr = start; /* this is technically too restrictive -- let's see if anyone complains */ while (*chptr && isprint(*chptr) && *chptr != ' ') chptr++; if (*chptr) { message(MESS_ERROR, "%s:%d bad %s path %s\n", configFile, lineNum, key, start); return NULL; } path = strdup(start); *endtag = oldchar, start = endtag; *startPtr = start; return path; } else return NULL; } static char * readAddress(const char * configFile, int lineNum, char * key, char ** startPtr) { char oldchar; char * endtag, * chptr; char * start = *startPtr; char * address; if (!isolateValue(configFile, lineNum, key, &start, &endtag)) { oldchar = *endtag, *endtag = '\0'; chptr = start; while (*chptr && isprint(*chptr) && *chptr != ' ') chptr++; if (*chptr) { message(MESS_ERROR, "%s:%d bad %s address %s\n", configFile, lineNum, key, start); return NULL; } address = strdup(start); *endtag = oldchar, start = endtag; *startPtr = start; return address; } else return NULL; } static int checkFile(const char * fname) { int i; /* Check if fname is '.' or '..'; if so, return false */ if (fname[0] == '.' && (!fname[1] || (fname[1] == '.' && !fname[2]))) return 0; /* Check if fname is ending in a taboo-extension; if so, return false */ for (i = 0; i < tabooCount; i++) { if (!strcmp(fname + strlen(fname) - strlen(tabooExts[i]), tabooExts[i])) { message(MESS_ERROR, "Ignoring %s, because of %s " "ending\n", fname, tabooExts[i]); return 0; } } /* All checks have been passed; return true */ return 1; } /* Used by qsort to sort filelist */ static int compar(const void *p, const void *q) { return strcoll(*((char **)p), *((char **)q)); } /* Free memory blocks pointed to by pointers in namelist and namelist itself */ static void free_namelist (char **namelist, int files_count) { int i; for (i=0; inext = malloc(sizeof(*item->next)); item->type = RP_STRING; item->arg = malloc(field - start + 1); strncpy(item->arg, start, field - start); item->arg[field-start] = '\0'; item = item->next; } item->next = malloc(sizeof(*item->next)); *item->next = new; item = item->next; memset(&new, 0, sizeof(new)); } field++; } if (start != field) { /* fixed string */ item->next = malloc(sizeof(*item->next)); item->type = RP_STRING; item->arg = malloc(field - start + 1); strncpy(item->arg, start, field-start); item->arg[field-start] = '\0'; item = item->next; } return NULL; } int readConfigPath(const char * path, logInfo * defConfig, logInfo ** logsPtr, int * numLogsPtr) { struct stat sb; int here; if (!tabooExts) { tabooExts = malloc(sizeof(*tabooExts) * defTabooCount); memcpy(tabooExts, defTabooExts, sizeof(*tabooExts) * defTabooCount); tabooCount = defTabooCount; } if (stat(path, &sb)) { message(MESS_ERROR, "cannot stat %s: %s\n", path, strerror(errno)); return 1; } if (S_ISDIR(sb.st_mode)) { char **namelist, **p; struct dirent *dp; int files_count,i; DIR *dirp; here = open(".", O_RDONLY); if (here < 0) { message(MESS_ERROR, "cannot open current directory: %s\n", strerror(errno)); return 1; } if ( (dirp = opendir(path)) == NULL) { message(MESS_ERROR, "cannot open directory %s: %s\n", path, strerror(errno)); return 1; } files_count = 0; namelist = NULL; while ((dp = readdir(dirp)) != NULL) { if (checkFile(dp->d_name)) { /* Realloc memory for namelist array if necessary */ if (files_count % REALLOC_STEP == 0) { p = (char **) realloc(namelist, (files_count + REALLOC_STEP) * sizeof(char *)); if (p) { namelist = p; memset(namelist + files_count, '\0', REALLOC_STEP * sizeof(char *)); } else { free_namelist(namelist, files_count); message(MESS_ERROR, "cannot realloc: %s\n", strerror(errno)); return 1; } } /* Alloc memory for file name */ if ( (namelist[files_count] = (char *) malloc( strlen(dp->d_name) + 1)) ) { strcpy(namelist[files_count], dp->d_name); files_count++; } else { free_namelist(namelist, files_count); message(MESS_ERROR, "cannot realloc: %s\n", strerror(errno)); return 1; } } } closedir( dirp ); if (files_count > 0) { qsort(namelist, files_count, sizeof(char *), compar); } else { return 0; } if (chdir(path)) { message(MESS_ERROR, "error in chdir(\"%s\"): %s\n", path, strerror(errno)); close(here); free_namelist(namelist, files_count); return 1; } for (i=0; iflags |= LOG_FLAG_COMPRESS; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "nocompress")) { newlog->flags &= ~LOG_FLAG_COMPRESS; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "delaycompress")) { newlog->flags |= LOG_FLAG_DELAYCOMPRESS; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "nodelaycompress")) { newlog->flags &= ~LOG_FLAG_DELAYCOMPRESS; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "sharedscripts")) { newlog->flags |= LOG_FLAG_SHAREDSCRIPTS; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "nosharedscripts")) { newlog->flags &= ~LOG_FLAG_SHAREDSCRIPTS; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "copytruncate")) { newlog->flags |= LOG_FLAG_COPYTRUNCATE; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "nocopytruncate")) { newlog->flags &= ~LOG_FLAG_COPYTRUNCATE; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "copy")) { newlog->flags |= LOG_FLAG_COPY; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "nocopy")) { newlog->flags &= ~LOG_FLAG_COPY; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "ifempty")) { newlog->flags |= LOG_FLAG_IFEMPTY; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "notifempty")) { newlog->flags &= ~LOG_FLAG_IFEMPTY; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "dateext")) { newlog->flags |= LOG_FLAG_DATEEXT; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "nodateext")) { newlog->flags &= ~LOG_FLAG_DATEEXT; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "noolddir")) { newlog->oldDir = NULL; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "mailfirst")) { newlog->flags |= LOG_FLAG_MAILFIRST; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "maillast")) { newlog->flags &= ~LOG_FLAG_MAILFIRST; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "create")) { *endtag = oldchar, start = endtag; endtag = start; while (*endtag != '\n') endtag++; while (isspace(*endtag)) endtag--; endtag++; oldchar = *endtag, *endtag = '\0'; rc = sscanf(start, "%o %s %s%c", &createMode, createOwner, createGroup, &foo); if (rc == 4) { message(MESS_ERROR, "%s:%d extra arguments for " "create\n", configFile, lineNum); return 1; } if (rc > 0) newlog->createMode = createMode; if (rc > 1) { pw = getpwnam(createOwner); if (!pw) { message(MESS_ERROR, "%s:%d unknown user '%s'\n", configFile, lineNum, createOwner); return 1; } newlog->createUid = pw->pw_uid; endpwent(); } if (rc > 2) { group = getgrnam(createGroup); if (!group) { message(MESS_ERROR, "%s:%d unknown group '%s'\n", configFile, lineNum, createGroup); return 1; } newlog->createGid = group->gr_gid; endgrent(); } newlog->flags |= LOG_FLAG_CREATE; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "nocreate")) { newlog->flags &= ~LOG_FLAG_CREATE; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "size")) { *endtag = oldchar, start = endtag; if (!isolateValue(configFile, lineNum, "size", &start, &endtag)) { oldchar = *endtag, *endtag = '\0'; length = strlen(start) - 1; if (start[length] == 'k') { start[length] = '\0'; multiplier = 1024; } else if (start[length] == 'M') { start[length] = '\0'; multiplier = 1024 * 1024; } else if (start[length] == 'G') { start[length] = '\0'; multiplier = 1024 * 1024 * 1024; } else if (!isdigit(start[length])) { message(MESS_ERROR, "%s:%d unknown unit '%c'\n", configFile, lineNum, start[length]); return 1; } else { multiplier = 1; } newlog->threshhold = multiplier * strtoul(start, &chptr, 0); if (*chptr) { message(MESS_ERROR, "%s:%d bad size '%s'\n", configFile, lineNum, start); return 1; } newlog->criterium = ROT_SIZE; *endtag = oldchar, start = endtag; } #if 0 /* this seems like such a good idea :-( */ } else if (!strcmp(start, "days")) { *endtag = oldchar, start = endtag; if (!isolateValue(configFile, lineNum, "size", &start, &endtag)) { oldchar = *endtag, *endtag = '\0'; newlog->threshhold = strtoul(start, &chptr, 0); if (*chptr) { message(MESS_ERROR, "%s:%d bad number of days'%s'\n", configFile, lineNum, start); return 1; } newlog->criterium = ROT_DAYS; *endtag = oldchar, start = endtag; } #endif } else if (!strcmp(start, "daily")) { *endtag = oldchar, start = endtag; newlog->criterium = ROT_DAYS; newlog->threshhold = 1; } else if (!strcmp(start, "monthly")) { *endtag = oldchar, start = endtag; newlog->criterium = ROT_MONTHLY; } else if (!strcmp(start, "weekly")) { *endtag = oldchar, start = endtag; newlog->criterium = ROT_WEEKLY; } else if (!strcmp(start, "rotate")) { *endtag = oldchar, start = endtag; if (!isolateValue(configFile, lineNum, "rotate count", &start, &endtag)) { oldchar = *endtag, *endtag = '\0'; newlog->rotateCount = strtoul(start, &chptr, 0); if (*chptr || newlog->rotateCount < 0) { message(MESS_ERROR, "%s:%d bad rotation count '%s'\n", configFile, lineNum, start); return 1; } *endtag = oldchar, start = endtag; } } else if (!strcmp(start, "start")) { *endtag = oldchar, start = endtag; if (!isolateValue(configFile, lineNum, "start count", &start, &endtag)) { oldchar = *endtag, *endtag = '\0'; newlog->logStart = strtoul(start, &chptr, 0); if (*chptr || newlog->logStart < 0) { message(MESS_ERROR, "%s:%d bad start count '%s'\n", configFile, lineNum, start); return 1; } *endtag = oldchar, start = endtag; } } else if (!strcmp(start, "maxage")) { *endtag = oldchar, start = endtag; if (!isolateValue(configFile, lineNum, "maxage count", &start, &endtag)) { oldchar = *endtag, *endtag = '\0'; newlog->rotateAge = strtoul(start, &chptr, 0); if (*chptr || newlog->rotateAge < 0) { message(MESS_ERROR, "%s:%d bad maximum age '%s'\n", configFile, lineNum, start); return 1; } *endtag = oldchar, start = endtag; } } else if (!strcmp(start, "errors")) { message(MESS_DEBUG, "%s: %d: the errors directive is deprecated and no longer used.\n", configFile, lineNum); } else if (!strcmp(start, "mail")) { *endtag = oldchar, start = endtag; if (!(newlog->logAddress = readAddress(configFile, lineNum, "mail", &start))) { return 1; } } else if (!strcmp(start, "nomail")) { /* hmmm, we could lose memory here, but not much */ newlog->logAddress = NULL; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "missingok")) { newlog->flags |= LOG_FLAG_MISSINGOK; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "nomissingok")) { newlog->flags &= ~LOG_FLAG_MISSINGOK; *endtag = oldchar, start = endtag; } else if (!strcmp(start, "prerotate")) { *endtag = oldchar, start = endtag; scriptStart = start; scriptDest = &newlog->pre; while (*start != '\n') start++; } else if (!strcmp(start, "firstaction")) { *endtag = oldchar, start = endtag; scriptStart = start; scriptDest = &newlog->first; while (*start != '\n') start++; } else if (!strcmp(start, "postrotate")) { *endtag = oldchar, start = endtag; scriptStart = start; scriptDest = &newlog->post; while (*start != '\n') start++; } else if (!strcmp(start, "lastaction")) { *endtag = oldchar, start = endtag; scriptStart = start; scriptDest = &newlog->last; while (*start != '\n') start++; } else if (!strcmp(start, "tabooext")) { if (newlog != defConfig) { message(MESS_ERROR, "%s:%d tabooext may not appear inside " "of log file definition\n", configFile, lineNum); return 1; } *endtag = oldchar, start = endtag; if (!isolateValue(configFile, lineNum, "tabooext", &start, &endtag)) { oldchar = *endtag, *endtag = '\0'; if (*start == '+') { start++; while (isspace(*start) && *start) start++; } else { free(tabooExts); tabooCount = 0; tabooExts = malloc(1); } while (*start) { chptr = start; while (!isspace(*chptr) && *chptr != ',' && *chptr) chptr++; tabooExts = realloc(tabooExts, sizeof(*tabooExts) * (tabooCount + 1)); /* this is a memory leak if the list gets reset */ tabooExts[tabooCount] = malloc(chptr - start + 1); strncpy(tabooExts[tabooCount], start, chptr - start); tabooExts[tabooCount][chptr - start] = '\0'; tabooCount++; start = chptr; if (*start == ',') start++; while (isspace(*start) && *start) start++; } *endtag = oldchar, start = endtag; } } else if (!strcmp(start, "include")) { if (newlog != defConfig) { message(MESS_ERROR, "%s:%d include may not appear inside " "of log file definition\n", configFile, lineNum); return 1; } *endtag = oldchar, start = endtag; if (!isolateValue(configFile, lineNum, "include", &start, &endtag)) { oldchar = *endtag, *endtag = '\0'; message(MESS_DEBUG, "including %s\n", start); if (readConfigPath(start, defConfig, logsPtr, numLogsPtr)) return 1; *endtag = oldchar, start = endtag; } } else if (!strcmp(start, "pattern")) { char * patternString; *endtag = oldchar, start = endtag; if (!(patternString = readPath(configFile, lineNum, "pattern", &start))) { return 1; } newlog->rotatePattern = parsePattern(patternString, configFile, lineNum); if (!newlog->rotatePattern) return 1; message(MESS_DEBUG, "pattern is now %s\n", patternString); } else if (!strcmp(start, "olddir")) { *endtag = oldchar, start = endtag; if (!(newlog->oldDir = readPath(configFile, lineNum, "olddir", &start))) { return 1; } #if 0 if (stat(newlog->oldDir, &sb)) { message(MESS_ERROR, "%s:%d error verifying olddir " "path %s: %s\n", configFile, lineNum, newlog->oldDir, strerror(errno)); return 1; } if (!S_ISDIR(sb.st_mode)) { message(MESS_ERROR, "%s:%d olddir path %s is not a " "directory\n", configFile, lineNum, newlog->oldDir); return 1; } #endif message(MESS_DEBUG, "olddir is now %s\n", newlog->oldDir); } else if (!strcmp(start, "extension")) { *endtag = oldchar, start = endtag; if (!isolateValue(configFile, lineNum, "extension name", &start, &endtag)) { oldchar = *endtag, *endtag = '\0'; newlog->extension = strdup(start); *endtag = oldchar, start = endtag; } message(MESS_DEBUG, "extension is now %s\n", newlog->extension); } else if (!strcmp(start, "compresscmd")) { *endtag = oldchar, start = endtag; if (!(newlog->compress_prog = readPath(configFile, lineNum, "compress", &start))) { return 1; } if (access(newlog->compress_prog, X_OK)) { message(MESS_ERROR, "%s:%d compression program %s is not an executable file\n", configFile, lineNum, newlog->compress_prog); return 1; } message(MESS_DEBUG, "compress_prog is now %s\n", newlog->compress_prog); } else if (!strcmp(start, "uncompresscmd")) { *endtag = oldchar, start = endtag; if (!(newlog->uncompress_prog = readPath(configFile, lineNum, "uncompress", &start))) { return 1; } if (access(newlog->uncompress_prog, X_OK)) { message(MESS_ERROR, "%s:%d uncompression program %s is not an executable file\n", configFile, lineNum, newlog->uncompress_prog); return 1; } message(MESS_DEBUG, "uncompress_prog is now %s\n", newlog->uncompress_prog); } else if (!strcmp(start, "compressoptions")) { char * options; *endtag = oldchar, start = endtag; if (!(options = readPath(configFile, lineNum, "compressoptions", &start))) { return 1; } if (poptParseArgvString(options, &newlog->compress_options_count, &newlog->compress_options_list)) { message(MESS_ERROR, "%s:%d invalid compression options\n", configFile, lineNum); return 1; } message(MESS_DEBUG, "compress_options is now %s\n", options); } else if (!strcmp(start, "compressext")) { *endtag = oldchar, start = endtag; if (!(newlog->compress_ext = readPath(configFile, lineNum, "compress-ext", &start))) { return 1; } message(MESS_DEBUG, "compress_ext is now %s\n", newlog->compress_ext); } else { message(MESS_ERROR, "%s:%d unknown option '%s' " "-- ignoring line\n", configFile, lineNum, start); *endtag = oldchar, start = endtag; } while (isblank(*start)) start++; if (*start != '\n') { message(MESS_ERROR, "%s:%d unexpected text\n", configFile, lineNum); while (*start != '\n') start++; } lineNum++; start++; } else if (*start == '/' || *start == '"' || *start == '\'') { if (newlog != defConfig) { message(MESS_ERROR, "%s:%d unexpected log filename\n", configFile, lineNum); return 1; } (*numLogsPtr)++; *logsPtr = realloc(*logsPtr, sizeof(**logsPtr) * *numLogsPtr); newlog = *logsPtr + *numLogsPtr - 1; memcpy(newlog, defConfig, sizeof(*newlog)); endtag = start; while (*endtag != '{' && *endtag != '\0') endtag++; if (*endtag != '{') { message(MESS_ERROR, "%s:%d missing end of line\n", configFile, lineNum); } *endtag = '\0'; if (poptParseArgvString(start, &argc, &argv)) { message(MESS_ERROR, "%s:%d error parsing filename\n", configFile, lineNum); return 1; } else if (argc < 1) { message(MESS_ERROR, "%s:%d { expected after log file name(s)\n", configFile, lineNum); return 1; } /* XXX this leaks the result of the glob */ newlog->files = NULL; newlog->numFiles = 0; for (argNum = 0; argNum < argc; argNum++) { rc = glob(argv[argNum], GLOB_NOCHECK, globerr, &globResult); if (rc == GLOB_ABORTED) { if(newlog->flags & LOG_FLAG_MISSINGOK) continue; message(MESS_ERROR, "%s:%d glob failed for %s\n", configFile, lineNum, argv[argNum]); return 1; } newlog->files = realloc(newlog->files, sizeof(*newlog->files) * (newlog->numFiles + globResult.gl_pathc)); for (i = 0; i < globResult.gl_pathc; i++) { /* if we glob directories we can get false matches */ if (!lstat(globResult.gl_pathv[i], &sb) && S_ISDIR(sb.st_mode)) continue; for (j = 0; j < *numLogsPtr - 1; j++) { for (k = 0; k < (*logsPtr)[j].numFiles; k++) { if (!strcmp((*logsPtr)[j].files[k], globResult.gl_pathv[i])) { message(MESS_ERROR, "%s:%d duplicate log entry for %s\n", configFile, lineNum, globResult.gl_pathv[i]); return 1; } } } newlog->files[newlog->numFiles] = globResult.gl_pathv[i]; newlog->numFiles++; } } newlog->pattern = strdup(start); message(MESS_DEBUG, "reading config info for %s\n", start); free(argv); start = endtag + 1; } else if (*start == '}') { if (newlog == defConfig) { message(MESS_ERROR, "%s:%d unxpected }\n", configFile, lineNum); return 1; } if (newlog->oldDir) { for (i = 0; i < newlog->numFiles; i++) { char *ld; dirName = ourDirName(newlog->files[i]); if (stat(dirName, &sb2)) { message(MESS_ERROR, "%s:%d error verifying log file " "path %s: %s\n", configFile, lineNum, dirName, strerror(errno)); free(dirName); return 1; } ld = alloca(strlen(dirName) + strlen(newlog->oldDir) + 2); sprintf(ld, "%s/%s", dirName, newlog->oldDir); free(dirName); if(newlog->oldDir[0] != '/') dirName = ld; else dirName = newlog->oldDir; if(stat(dirName, &sb)) { message(MESS_ERROR, "%s:%d error verifying olddir " "path %s: %s\n", configFile, lineNum, dirName, strerror(errno)); return 1; } if (sb.st_dev != sb2.st_dev) { message(MESS_ERROR, "%s:%d olddir %s and log file %s " "are on different devices\n", configFile, lineNum, newlog->oldDir, newlog->files[i]); return 1; } } } newlog = defConfig; start++; while (isblank(*start)) start++; if (*start != '\n') { message(MESS_ERROR, "%s:%d, unexpected text after {\n", configFile, lineNum); } } else { message(MESS_ERROR, "%s:%d lines must begin with a keyword " "or a filename (possibly in double quotes)\n", configFile, lineNum); while (*start != '\n') start++; lineNum++; start++; } } if(scriptStart) { message(MESS_ERROR, "%s:prerotate or postrotate without endscript\n", configFile); return 1; } return 0; }