diff --git a/config.c b/config.c index e6b6a53..e9992e3 100644 --- a/config.c +++ b/config.c @@ -220,6 +220,61 @@ static char *readPath(const char *configFile, int lineNum, char *key, return NULL; } +static int readModeUidGid(const char *configFile, int lineNum, char *key, + const char *directive, mode_t *mode, uid_t *uid, + gid_t *gid) { + char u[200], g[200]; + int m; + char tmp; + int rc; + struct group *group; + struct passwd *pw = NULL; + + rc = sscanf(key, "%o %199s %199s%c", &m, u, g, &tmp); + /* We support 'key notation now */ + if (rc == 0) { + rc = sscanf(key, "%199s %199s%c", u, g, &tmp); + /* Simulate that we have read mode and keep the default value. */ + if (rc > 0) { + m = *mode; + rc += 1; + } + } + + if (rc == 4) { + message(MESS_ERROR, "%s:%d extra arguments for " + "%s\n", configFile, lineNum, directive); + return -1; + } + + if (rc > 0) { + *mode = m; + } + + if (rc > 1) { + pw = getpwnam(u); + if (!pw) { + message(MESS_ERROR, "%s:%d unknown user '%s'\n", + configFile, lineNum, u); + return -1; + } + *uid = pw->pw_uid; + endpwent(); + } + if (rc > 2) { + group = getgrnam(g); + if (!group) { + message(MESS_ERROR, "%s:%d unknown group '%s'\n", + configFile, lineNum, g); + return -1; + } + *gid = group->gr_gid; + endgrent(); + } + + return 0; +} + static char *readAddress(const char *configFile, int lineNum, char *key, char **startPtr, char **buf, size_t length) { @@ -249,6 +304,55 @@ static char *readAddress(const char *configFile, int lineNum, char *key, return NULL; } +static int do_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid) { + struct stat sb; + + if (stat(path, &sb) != 0) { + if (mkdir(path, mode) != 0 && errno != EEXIST) { + message(MESS_ERROR, "error creating %s: %s\n", + path, strerror(errno)); + return -1; + } + if ((uid != sb.st_uid || gid != sb.st_gid) && + chown(path, uid, gid)) { + message(MESS_ERROR, "error setting owner of %s to uid %d and gid %d: %s\n", + path, uid, gid, strerror(errno)); + return -1; + } + } + else if (!S_ISDIR(sb.st_mode)) { + message(MESS_ERROR, "path %s already exists, but it is not a directory\n", + path); + errno = ENOTDIR; + return -1; + } + + return 0; +} + +static int mkpath(const char *path, mode_t mode, uid_t uid, gid_t gid) { + char *pp; + char *sp; + int rv; + char *copypath = strdup(path); + + rv = 0; + pp = copypath; + while (rv == 0 && (sp = strchr(pp, '/')) != 0) { + if (sp != pp) { + *sp = '\0'; + rv = do_mkdir(copypath, mode, uid, gid); + *sp = '/'; + } + pp = sp + 1; + } + if (rv == 0) { + rv = do_mkdir(path, mode, uid, gid); + } + free(copypath); + return rv; +} + static int checkFile(const char *fname) { int i; @@ -330,6 +434,9 @@ static void copyLogInfo(struct logInfo *to, struct logInfo *from) to->createGid = from->createGid; to->suUid = from->suUid; to->suGid = from->suGid; + to->olddirMode = from->olddirMode; + to->olddirUid = from->olddirUid; + to->olddirGid = from->olddirGid; if (from->compress_options_count) { poptDupArgv(from->compress_options_count, from->compress_options_list, &to->compress_options_count, &to->compress_options_list); @@ -539,6 +646,11 @@ int readAllConfigPaths(const char **paths) .createMode = NO_MODE, .createUid = NO_UID, .createGid = NO_GID, + .olddirMode = NO_MODE, + .olddirUid = NO_UID, + .olddirGid = NO_GID, + .suUid = NO_UID, + .suGid = NO_GID, .compress_options_list = NULL, .compress_options_count = 0 }; @@ -579,13 +691,19 @@ static int globerr(const char *pathname, int theerr) free(newlog->what); \ newlog->what = NULL; \ } while (0); +#define RAISE_ERROR() \ + if (newlog != defConfig) { \ + state = STATE_ERROR; \ + continue; \ + } else { \ + goto error; \ + } #define MAX_NESTING 16U static int readConfigFile(const char *configFile, struct logInfo *defConfig) { int fd; char *buf, *endtag, *key = NULL; - char foo; off_t length; int lineNum = 1; unsigned long long multiplier; @@ -595,11 +713,8 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) struct logInfo *newlog = defConfig; char *start, *chptr; char *dirName; - struct group *group; struct passwd *pw = NULL; int rc; - char createOwner[200], createGroup[200]; - int createMode; struct stat sb, sb2; glob_t globResult; const char **argv; @@ -611,6 +726,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) static unsigned recursion_depth = 0U; char *globerr_msg = NULL; int in_config = 0; + int rv; struct flock fd_lock = { .l_start = 0, .l_len = 0, @@ -807,53 +923,22 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) } else if (!strcmp(key, "maillast")) { newlog->flags &= ~LOG_FLAG_MAILFIRST; } else if (!strcmp(key, "su")) { + mode_t tmp_mode = NO_MODE; free(key); key = isolateLine(&start, &buf, length); if (key == NULL) continue; - rc = sscanf(key, "%199s %199s%c", createOwner, - createGroup, &foo); - if (rc == 3) { - message(MESS_ERROR, "%s:%d extra arguments for " - "su\n", configFile, lineNum); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + rv = readModeUidGid(configFile, lineNum, key, "su", + &tmp_mode, &newlog->suUid, + &newlog->suGid); + if (rv == -1) { + RAISE_ERROR(); } - - if (rc > 0) { - pw = getpwnam(createOwner); - if (!pw) { - message(MESS_ERROR, "%s:%d unknown user '%s'\n", - configFile, lineNum, createOwner); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } - } - newlog->suUid = pw->pw_uid; - endpwent(); - } - if (rc > 1) { - group = getgrnam(createGroup); - if (!group) { - message(MESS_ERROR, "%s:%d unknown group '%s'\n", - configFile, lineNum, createGroup); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } - } - newlog->suGid = group->gr_gid; - endgrent(); + else if (tmp_mode != NO_MODE) { + message(MESS_ERROR, "%s:%d extra arguments for " + "su\n", configFile, lineNum); + RAISE_ERROR(); } newlog->flags |= LOG_FLAG_SU; @@ -863,65 +948,30 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) if (key == NULL) continue; - rc = sscanf(key, "%o %199s %199s%c", &createMode, - createOwner, createGroup, &foo); - /* We support 'create notation now */ - if (rc == 0) { - rc = sscanf(key, "%199s %199s%c", - createOwner, createGroup, &foo); - /* Simulate that we have read createMode and se it - * to NO_MODE. */ - if (rc > 0) { - createMode = NO_MODE; - rc += 1; - } - } - if (rc == 4) { - message(MESS_ERROR, "%s:%d extra arguments for " - "create\n", configFile, lineNum); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + rv = readModeUidGid(configFile, lineNum, key, "create", + &newlog->createMode, &newlog->createUid, + &newlog->createGid); + if (rv == -1) { + RAISE_ERROR(); } - 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); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } - } - 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); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } - } - newlog->createGid = group->gr_gid; - endgrent(); + newlog->flags |= LOG_FLAG_CREATE; + } else if (!strcmp(key, "createolddir")) { + free(key); + key = isolateLine(&start, &buf, length); + if (key == NULL) + continue; + + rv = readModeUidGid(configFile, lineNum, key, "createolddir", + &newlog->olddirMode, &newlog->olddirUid, + &newlog->olddirGid); + if (rv == -1) { + RAISE_ERROR(); } - newlog->flags |= LOG_FLAG_CREATE; + newlog->flags |= LOG_FLAG_OLDDIRCREATE; + } else if (!strcmp(key, "nocreateolddir")) { + newlog->flags &= ~LOG_FLAG_OLDDIRCREATE; } else if (!strcmp(key, "nocreate")) { newlog->flags &= ~LOG_FLAG_CREATE; } else if (!strcmp(key, "size") || !strcmp(key, "minsize") || @@ -945,12 +995,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) free(opt); message(MESS_ERROR, "%s:%d unknown unit '%c'\n", configFile, lineNum, key[l]); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } else { multiplier = 1; } @@ -960,12 +1005,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) message(MESS_ERROR, "%s:%d bad size '%s'\n", configFile, lineNum, key); free(opt); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } if (!strncmp(opt, "size", 4)) { newlog->criterium = ROT_SIZE; @@ -1015,12 +1055,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) message(MESS_ERROR, "%s:%d bad rotation count '%s'\n", configFile, lineNum, key); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } } else continue; @@ -1034,12 +1069,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) if (*chptr || newlog->logStart < 0) { message(MESS_ERROR, "%s:%d bad start count '%s'\n", configFile, lineNum, key); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } } else continue; @@ -1052,12 +1082,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) if (*chptr || newlog->rotateAge < 0) { message(MESS_ERROR, "%s:%d bad maximum age '%s'\n", configFile, lineNum, start); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } } else continue; @@ -1069,12 +1094,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) freeLogItem(logAddress); if (!(newlog->logAddress = readAddress(configFile, lineNum, "mail", &start, &buf, length))) { - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } else continue; } else if (!strcmp(key, "nomail")) { @@ -1177,31 +1197,8 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) if (!(newlog->oldDir = readPath(configFile, lineNum, "olddir", &start, &buf, length))) { - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } - } - -#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)); - free(newlog->oldDir); - goto error; + RAISE_ERROR(); } - - if (!S_ISDIR(sb.st_mode)) { - message(MESS_ERROR, "%s:%d olddir path %s is not a " - "directory\n", configFile, lineNum, - newlog->oldDir); - free(newlog->oldDir); - goto error; - } -#endif message(MESS_DEBUG, "olddir is now %s\n", newlog->oldDir); } else if (!strcmp(key, "extension")) { if ((key = isolateValue @@ -1222,24 +1219,14 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) if (! (newlog->compress_prog = readPath(configFile, lineNum, "compress", &start, &buf, length))) { - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } 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); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } message(MESS_DEBUG, "compress_prog is now %s\n", @@ -1252,24 +1239,14 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) (newlog->uncompress_prog = readPath(configFile, lineNum, "uncompress", &start, &buf, length))) { - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } 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); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } message(MESS_DEBUG, "uncompress_prog is now %s\n", @@ -1285,12 +1262,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) } if (!(options = isolateLine(&start, &buf, length))) { - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } if (poptParseArgvString(options, @@ -1300,12 +1272,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) "%s:%d invalid compression options\n", configFile, lineNum); free(options); - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } message(MESS_DEBUG, "compress_options is now %s\n", @@ -1318,12 +1285,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) (newlog->compress_ext = readPath(configFile, lineNum, "compress-ext", &start, &buf, length))) { - if (newlog != defConfig) { - state = STATE_ERROR; - continue; - } else { - goto error; - } + RAISE_ERROR(); } message(MESS_DEBUG, "compress_ext is now %s\n", @@ -1484,45 +1446,57 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig) globerr_msg = NULL; if (!(newlog->flags & LOG_FLAG_MISSINGOK)) goto error; - } + } - if (newlog->oldDir) { + if (newlog->oldDir) { for (i = 0; i < newlog->numFiles; i++) { char *ld; + int rv; 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); - goto error; + message(MESS_ERROR, + "%s:%d error verifying log file " + "path %s: %s\n", configFile, lineNum, + dirName, strerror(errno)); + free(dirName); + goto error; } - ld = alloca(strlen(dirName) + strlen(newlog->oldDir) + - 2); + 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)); - goto error; + if (newlog->oldDir[0] != '/') { + dirName = ld; + } + else { + dirName = newlog->oldDir; + } + + rv = stat(dirName, &sb); + if (rv) { + if (errno == ENOENT && newlog->flags & LOG_FLAG_OLDDIRCREATE) { + if (mkpath(dirName, newlog->olddirMode, + newlog->olddirUid, newlog->olddirGid)) { + goto error; + } + } + else { + message(MESS_ERROR, "%s:%d error verifying olddir " + "path %s: %s\n", configFile, lineNum, + dirName, strerror(errno)); + goto error; + } } 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]); - goto error; + message(MESS_ERROR, + "%s:%d olddir %s and log file %s " + "are on different devices\n", configFile, + lineNum, newlog->oldDir, newlog->files[i]); + goto error; } } - } + } newlog = defConfig; state = STATE_DEFINITION_END; diff --git a/logrotate.8 b/logrotate.8 index 8cb9c2f..2cd2370 100644 --- a/logrotate.8 +++ b/logrotate.8 @@ -216,6 +216,16 @@ file for the omitted attributes. This option can be disabled using the \fBnocreate\fR option. .TP +\fBcreateolddir \fImode\fR \fIowner\fR \fIgroup\fR +If the directory specified by \fBolddir\fR directive does not exist, it is +created. \fImode\fR specifies the mode for the \fBolddir\fR directory +in octal (the same as \fBchmod\fR(2)), \fIowner\fR specifies the user name +who will own the \fBolddir\fR directory, and \fIgroup\fR specifies the group +the \fBolddir\fR directory will belong to. This option can be disabled using the +\fBnocreateolddir\fR option. + + +.TP \fBdaily\fR Log files are rotated every day. @@ -350,6 +360,10 @@ Do not truncate the original log file in place after creating a copy New log files are not created (this overrides the \fBcreate\fR option). .TP +\fBnocreateolddir\fR +\fBolddir\fR directory is not created by logrotate when it does not exist. + +.TP \fBnodelaycompress\fR Do not postpone compression of the previous log file to the next rotation cycle (this overrides the \fBdelaycompress\fR option). diff --git a/logrotate.h b/logrotate.h index 813418e..cf42703 100644 --- a/logrotate.h +++ b/logrotate.h @@ -20,6 +20,7 @@ #define LOG_FLAG_SHRED (1 << 10) #define LOG_FLAG_SU (1 << 11) #define LOG_FLAG_DATEYESTERDAY (1 << 12) +#define LOG_FLAG_OLDDIRCREATE (1 << 13) #define NO_MODE ((mode_t) -1) #define NO_UID ((uid_t) -1) @@ -55,6 +56,9 @@ struct logInfo { gid_t createGid; uid_t suUid; /* switch user to this uid and group to this gid */ gid_t suGid; + mode_t olddirMode; + uid_t olddirUid; + uid_t olddirGid; /* these are at the end so they end up nil */ const char **compress_options_list; int compress_options_count; diff --git a/test/test b/test/test index 25b76a6..e9ce46f 100755 --- a/test/test +++ b/test/test @@ -453,9 +453,15 @@ cleanup 13 # ------------------------------- Test 13 ------------------------------------ preptest test.log 13 1 0 rm -rf testdir -mkdir testdir $RLR test-config.13 --force +ls -l|grep testdir|grep "drwx------." 2>/dev/null >/dev/null +if [ $? != 0 ]; then + echo "testdir should have mode 2700, but it has:" + ls -l|grep testdir + exit 3 +fi + checkoutput </dev/null if [ $? != 0 ]; then - echo "test.log must not contain user:nobody:rwx" + echo "test.log must contain user:nobody:rwx" + getfacl test.log exit 3 fi getfacl test.log.1|grep "user:nobody:rwx" >/dev/null if [ $? != 0 ]; then echo "test.log.1 must contain user:nobody:rwx" + getfacl test.log.1 exit 3 fi diff --git a/test/test-config.13.in b/test/test-config.13.in index 31a29ef..dc2efd5 100644 --- a/test/test-config.13.in +++ b/test/test-config.13.in @@ -4,4 +4,5 @@ create monthly rotate 1 olddir &DIR&/testdir + createolddir 700 &USER& &GROUP& }