From 90beb7e48cc1badb903a58ff6abba435734a47cd Mon Sep 17 00:00:00 2001 From: cgd Date: Fri, 21 May 1993 14:43:59 +0000 Subject: [PATCH] initial import of this log-rotation program to NetBSD --- usr.bin/newsyslog/Makefile | 18 + usr.bin/newsyslog/newsyslog.8 | 188 +++++++++++ usr.bin/newsyslog/newsyslog.c | 600 ++++++++++++++++++++++++++++++++++ 3 files changed, 806 insertions(+) create mode 100644 usr.bin/newsyslog/Makefile create mode 100644 usr.bin/newsyslog/newsyslog.8 create mode 100644 usr.bin/newsyslog/newsyslog.c diff --git a/usr.bin/newsyslog/Makefile b/usr.bin/newsyslog/Makefile new file mode 100644 index 000000000000..14fbbf991f58 --- /dev/null +++ b/usr.bin/newsyslog/Makefile @@ -0,0 +1,18 @@ +# $Id: Makefile,v 1.1 1993/05/21 14:43:59 cgd Exp $ + +PROG= newsyslog + +CFLAGS+= -DOSF +CFLAGS+= -DCONF=\"/etc/newsyslog.conf\" +CFLAGS+= -DPIDFILE=\"/var/run/syslog.pid\" +CFLAGS+= -DCOMPRESS=\"/usr/bin/gzip\" + +MAN5= newsyslog.conf.0 +MAN8= newsyslog.0 + +CLEANFILES+= newsyslog.conf.5 + +newsyslog.conf.5: + ln -s ${.CURDIR}/newsyslog.8 $@ + +.include diff --git a/usr.bin/newsyslog/newsyslog.8 b/usr.bin/newsyslog/newsyslog.8 new file mode 100644 index 000000000000..0e00ccdf2699 --- /dev/null +++ b/usr.bin/newsyslog/newsyslog.8 @@ -0,0 +1,188 @@ +.TH NEWSYSLOG 8 "January 12, 1989" "Project Athena" +.ns +.\" This file contains changes from the Open Software Foundation. +.\" The RCS history log will appear at the end of this file. +.\" @(#)newsyslog.8 $Revision: 1.1 $ $Date: 1993/05/21 14:44:00 $ $Locker: $ +.\" +.\" +.\" Copyright 1991 by the Massachusetts Institute of Technology +.\" +.\" All rights reserved. +.\" +.\" Permission to use, copy, modify, and distribute this software and its +.\" documentation for any purpose and without fee is hereby granted, +.\" provided that the above copyright notice appear in all copies and that +.\" both that copyright notice and this permission notice appear in +.\" supporting documentation, and that the name of the Massachusetts +.\" Institute of Technology (M.I.T.) not be used in advertising or publicity +.\" pertaining to distribution of the software without specific, written +.\" prior permission. +.\" +.\" M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +.\" ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +.\" M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +.\" ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +.\" WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +.\" ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +.\" SOFTWARE. +.\" +.sp +.SH NAME +newsyslog \- maintain system log files to manageable sizes +.SH SYNOPSIS +.B /usr/local/sbin/newsyslog +[ +.B \-vnr +] [ +.B \-f +.I configuration file +] +.SH DESCRIPTION +.I Newsyslog +is a program that should be scheduled to run periodically by +.IR crontab . +When it is executed it archives log files if necessary. If a log file +is determined to require archiving, +.I newsyslog +rearranges the files so that ``logfile'' is empty, ``logfile.0'' has +the last period's logs in it, ``logfile.1'' has the next to last +period's logs in it, and so on, up to a user-specified number of +archived logs. Optionally the archived logs can be compressed to save +space. +.PP +A log can be archived because of two reasons. The log file can have +grown bigger than a preset size in kilobytes, or a preset number of +hours may have elapsed since the last log archive. The granularity of +.I newsyslog +is dependent on how often it is scheduled to run in crontab. Since +the program is quite fast, it may be scheduled to run every hour +without any ill effects. +.PP +When starting up, +.I newsyslog +reads in a configuration file to determine which logs should be looked +at. By default, this configuration file is +.IR /var/adm/newsyslog.conf . +Each line of the file contains information about a particular log file +that should be handled by +.IR newsyslog . +Each line has five mandatory fields and two optional fields, with a +whitespace separating each field. Blank lines or lines beginning with +``#'' are ignored. The fields of the configuration file are as +follows: +.br + logfile name +.br + owner.group of archives (optional) +.br + mode of logfile & archives +.br + number of archives +.br + size of archives +.br + archive interval +.br + flags (optional) +.PP +The +.I logfile name +entry is the name of the system log file to be archived. +.PP +The optional +.I owner.group +entry specifies an ownership and group for the archive file. +The "." is essential, even if the +.I owner +or +.I group +field is left blank. The +fields may be numeric, or a name which is looked up in +.I /etc/passwd +or +.IR /etc/group . +.PP +The +.I number of archives +entry specifies the number of archives to be kept besides the log file +itself. +.PP +When the size of the logfile reaches +.I size of +.IR archives , +the logfile becomes trimmed as described above. If this field is +replaced by a ``*'', then the size of the logfile is not taken into +account when determining when to trim the log file. +.PP +The +.I number of hours +entry specifies the time separation between the trimming of the log +file. If this field is replaced by a ``*'', the the number of hours +since the last time the log was trimmed will not be taken into +consideration. +.PP +The +.I flags +field specifies if the archives should have any special processing +done to the archived log files. The ``Z'' flag will make the archive +files compressed to save space using /usr/ucb/compress. The ``B'' flag +means that the file is a binary file, and so the ascii message which +.I newsyslog +inserts to indicate the fact that the logs have been turned over +should not be included. +.PP +.SH OPTIONS +The following options can be used with newsyslog: +.TP +.B \-f \fIconfig-file +instructs newsyslog to use +.I config-file +instead of /var/adm/newsyslog.conf for its configuration file. +.TP +.B \-v +places +.I newsyslog +in verbose mode. In this mode it will print out each log and its +reasons for either trimming that log or skipping it. +.TP +.B \-n +causes +.I newsyslog +not to trim the logs, but to print out what it would do if this option +were not specified. +.TP +.B \-r +removes the restriction that +.I newsyslog +must be running as root. Of course, +.I newsyslog +will not be able to send a HUP signal to +.IR syslogd , +so this option should only be used in debugging. +.SH FILES +/var/adm/newsyslog.conf +.SH BUGS +Doesn't yet automatically read the logs to find security breaches. + + +.SH AUTHOR +Theodore Ts'o, MIT Project Athena +.br +Copyright 1987, Massachusetts Institute of Technology +.SH "SEE ALSO" +syslogd(8), syslog(3), compress(1) +.ns +.\" HISTORY +.\" $Log: newsyslog.8,v $ +.\" Revision 1.1 1993/05/21 14:44:00 cgd +.\" initial import of this log-rotation program to NetBSD +.\" +.\" Revision 3.0 1993/01/01 07:39:16 ede +.\" Initial revision for OSF/1 R1.3 +.\" +.\" Revision 1.2 1991/08/16 09:50:23 devrcs +.\" From John Brezak, brezak@apollo.com, originally from Project Athena +.\" [91/07/24 09:33:14 meissner] +.\" +.\" $EndLog$ +.sp diff --git a/usr.bin/newsyslog/newsyslog.c b/usr.bin/newsyslog/newsyslog.c new file mode 100644 index 000000000000..e7e9cbd664d0 --- /dev/null +++ b/usr.bin/newsyslog/newsyslog.c @@ -0,0 +1,600 @@ +/* + * This file contains changes from the Open Software Foundation. + * The RCS history log will appear at the end of this file. + * @(#)newsyslog.c $Revision: 1.1 $ $Date: 1993/05/21 14:44:02 $ $Locker: $ + */ + +/* + +Copyright 1991 by the Massachusetts Institute of Technology + +All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of the Massachusetts +Institute of Technology (M.I.T.) not be used in advertising or publicity +pertaining to distribution of the software without specific, written +prior permission. + +M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +*/ + +/* + * newsyslog - roll over selected logs at the appropriate time, + * keeping the a specified number of backup files around. + * + * $Source: /cvsroot/src/usr.bin/newsyslog/newsyslog.c,v $ + * $Author: cgd $ + */ + +#if !defined(lint) && !defined(_NOIDENT) +static char rcsid[] = "@(#)$RCSfile: newsyslog.c,v $ $Revision: 1.1 $ (OSF) $Date: 1993/05/21 14:44:02 $"; +#endif + +#ifndef CONF +#define CONF "/etc/athena/newsyslog.conf" /* Configuration file */ +#endif +#ifndef PIDFILE +#define PIDFILE "/etc/syslog.pid" +#endif +#ifndef COMPRESS +#define COMPRESS "/usr/ucb/compress" /* File compression program */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define kbytes(size) (((size) + 1023) >> 10) +#ifdef _IBMR2 +/* Calculates (db * DEV_BSIZE) */ +#define dbtob(db) ((unsigned)(db) << UBSHIFT) +#endif + +#define CE_COMPACT 1 /* Compact the achived log files */ +#define CE_BINARY 2 /* Logfile is in binary, don't add */ + /* status messages */ +#define NONE -1 + +struct conf_entry { + char *log; /* Name of the log */ + int uid; /* Owner of log */ + int gid; /* Group of log */ + int numlogs; /* Number of logs to keep */ + int size; /* Size cutoff to trigger trimming the log */ + int hours; /* Hours between log trimming */ + int permissions; /* File permissions on the log */ + int flags; /* Flags (CE_COMPACT & CE_BINARY) */ + struct conf_entry *next; /* Linked list pointer */ +}; + +extern int optind; +extern char *optarg; +extern char *malloc(); +extern uid_t getuid(),geteuid(); +extern time_t time(); + +char *progname; /* contains argv[0] */ +int verbose = 0; /* Print out what's going on */ +int needroot = 1; /* Root privs are necessary */ +int noaction = 0; /* Don't do anything, just show it */ +char *conf = CONF; /* Configuration file to use */ +time_t timenow; +int syslog_pid; /* read in from /etc/syslog.pid */ +char hostname[64]; /* hostname */ +char *daytime; /* timenow in human readable form */ + + +struct conf_entry *parse_file(); +char *sob(), *son(), *strdup(), *missing_field(); + +main(argc,argv) + int argc; + char **argv; +{ + struct conf_entry *p, *q; + + PRS(argc,argv); + if (needroot && getuid() && geteuid()) { + fprintf(stderr,"%s: must have root privs\n",progname); + exit(1); + } + p = q = parse_file(); + while (p) { + do_entry(p); + p=p->next; + free((char *) q); + q=p; + } + exit(0); +} + +do_entry(ent) + struct conf_entry *ent; + +{ + int size, modtime; + + if (verbose) { + if (ent->flags & CE_COMPACT) + printf("%s <%dZ>: ",ent->log,ent->numlogs); + else + printf("%s <%d>: ",ent->log,ent->numlogs); + } + size = sizefile(ent->log); + modtime = age_old_log(ent->log); + if (size < 0) { + if (verbose) + printf("does not exist.\n"); + } else { + if (verbose && (ent->size > 0)) + printf("size (Kb): %d [%d] ", size, ent->size); + if (verbose && (ent->hours > 0)) + printf(" age (hr): %d [%d] ", modtime, ent->hours); + if (((ent->size > 0) && (size >= ent->size)) || + ((ent->hours > 0) && ((modtime >= ent->hours) + || (modtime < 0)))) { + if (verbose) + printf("--> trimming log....\n"); + if (noaction && !verbose) { + if (ent->flags & CE_COMPACT) + printf("%s <%dZ>: trimming", + ent->log,ent->numlogs); + else + printf("%s <%d>: trimming", + ent->log,ent->numlogs); + } + dotrim(ent->log, ent->numlogs, ent->flags, + ent->permissions, ent->uid, ent->gid); + } else { + if (verbose) + printf("--> skipping\n"); + } + } +} + +PRS(argc,argv) + int argc; + char **argv; +{ + int c; + FILE *f; + char line[BUFSIZ]; + + progname = argv[0]; + timenow = time((time_t *) 0); + daytime = ctime(&timenow); + daytime[strlen(daytime)-1] = '\0'; + + /* Let's find the pid of syslogd */ + syslog_pid = 0; + f = fopen(PIDFILE,"r"); + if (f && fgets(line,BUFSIZ,f)) + syslog_pid = atoi(line); + + /* Let's get our hostname */ + if (gethostname(hostname, 64)) { + perror("gethostname"); + (void) strcpy(hostname,"Mystery Host"); + } + + optind = 1; /* Start options parsing */ + while ((c=getopt(argc,argv,"nrvf:t:")) != EOF) + switch (c) { + case 'n': + noaction++; /* This implies needroot as off */ + /* fall through */ + case 'r': + needroot = 0; + break; + case 'v': + verbose++; + break; + case 'f': + conf = optarg; + break; + default: + usage(); + } + } + +usage() +{ + fprintf(stderr, + "Usage: %s <-nrv> <-f config-file>\n"); + exit(1); +} + +/* Parse a configuration file and return a linked list of all the logs + * to process + */ +struct conf_entry *parse_file() +{ + FILE *f; + char line[BUFSIZ], *parse, *q; + char *errline, *group; + struct conf_entry *first = NULL; + struct conf_entry *working; + struct passwd *pass; + struct group *grp; + + if (strcmp(conf,"-")) + f = fopen(conf,"r"); + else + f = stdin; + if (!f) { + (void) fprintf(stderr,"%s: ",progname); + perror(conf); + exit(1); + } + while (fgets(line,BUFSIZ,f)) { + if ((line[0]== '\n') || (line[0] == '#')) + continue; + errline = strdup(line); + if (!first) { + working = (struct conf_entry *) malloc(sizeof(struct conf_entry)); + first = working; + } else { + working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry)); + working = working->next; + } + + q = parse = missing_field(sob(line),errline); + *(parse = son(line)) = '\0'; + working->log = strdup(q); + + q = parse = missing_field(sob(++parse),errline); + *(parse = son(parse)) = '\0'; + if ((group = index(q, '.')) != NULL) { + *group++ = '\0'; + if (*q) { + if (!(isnumber(q))) { + if ((pass = getpwnam(q)) == NULL) { + fprintf(stderr, + "Error in config file; unknown user:\n"); + fputs(errline,stderr); + exit(1); + } + working->uid = pass->pw_uid; + } else + working->uid = atoi(q); + } else + working->uid = NONE; + + q = group; + if (*q) { + if (!(isnumber(q))) { + if ((grp = getgrnam(q)) == NULL) { + fprintf(stderr, + "Error in config file; unknown group:\n"); + fputs(errline,stderr); + exit(1); + } + working->gid = grp->gr_gid; + } else + working->gid = atoi(q); + } else + working->gid = NONE; + + q = parse = missing_field(sob(++parse),errline); + *(parse = son(parse)) = '\0'; + } + else + working->uid = working->gid = NONE; + + if (!sscanf(q,"%o",&working->permissions)) { + fprintf(stderr, + "Error in config file; bad permissions:\n"); + fputs(errline,stderr); + exit(1); + } + + q = parse = missing_field(sob(++parse),errline); + *(parse = son(parse)) = '\0'; + if (!sscanf(q,"%d",&working->numlogs)) { + fprintf(stderr, + "Error in config file; bad number:\n"); + fputs(errline,stderr); + exit(1); + } + + q = parse = missing_field(sob(++parse),errline); + *(parse = son(parse)) = '\0'; + if (isdigit(*q)) + working->size = atoi(q); + else + working->size = -1; + + q = parse = missing_field(sob(++parse),errline); + *(parse = son(parse)) = '\0'; + if (isdigit(*q)) + working->hours = atoi(q); + else + working->hours = -1; + + q = parse = sob(++parse); /* Optional field */ + *(parse = son(parse)) = '\0'; + working->flags = 0; + while (q && *q && !isspace(*q)) { + if ((*q == 'Z') || (*q == 'z')) + working->flags |= CE_COMPACT; + else if ((*q == 'B') || (*q == 'b')) + working->flags |= CE_BINARY; + else { + fprintf(stderr, + "Illegal flag in config file -- %c\n", + *q); + exit(1); + } + q++; + } + + free(errline); + } + if (working) + working->next = (struct conf_entry *) NULL; + (void) fclose(f); + return(first); +} + +char *missing_field(p,errline) + char *p,*errline; +{ + if (!p || !*p) { + fprintf(stderr,"Missing field in config file:\n"); + fputs(errline,stderr); + exit(1); + } + return(p); +} + +dotrim(log,numdays,flags,perm,owner_uid,group_gid) + char *log; + int numdays; + int flags; + int perm; + int owner_uid; + int group_gid; +{ + char file1[128], file2[128]; + char zfile1[128], zfile2[128]; + int fd; + struct stat st; + +#ifdef _IBMR2 +/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */ +/* change it to be owned by uid -1, instead of leaving it as is, as it is */ +/* supposed to. */ + if (owner_uid == -1) + owner_uid = geteuid(); +#endif + + /* Remove oldest log */ + (void) sprintf(file1,"%s.%d",log,numdays); + (void) strcpy(zfile1, file1); + (void) strcat(zfile1, ".Z"); + + if (noaction) { + printf("rm -f %s\n", file1); + printf("rm -f %s\n", zfile1); + } else { + (void) unlink(file1); + (void) unlink(zfile1); + } + + /* Move down log files */ + while (numdays--) { + (void) strcpy(file2,file1); + (void) sprintf(file1,"%s.%d",log,numdays); + (void) strcpy(zfile1, file1); + (void) strcpy(zfile2, file2); + if (lstat(file1, &st)) { + (void) strcat(zfile1, ".Z"); + (void) strcat(zfile2, ".Z"); + if (lstat(zfile1, &st)) continue; + } + if (noaction) { + printf("mv %s %s\n",zfile1,zfile2); + printf("chmod %o %s\n", perm, zfile2); + printf("chown %d.%d %s\n", + owner_uid, group_gid, zfile2); + } else { + (void) rename(zfile1, zfile2); + (void) chmod(zfile2, perm); + (void) chown(zfile2, owner_uid, group_gid); + } + } + if (!noaction && !(flags & CE_BINARY)) + (void) log_trim(log); /* Report the trimming to the old log */ + + if (noaction) + printf("mv %s to %s\n",log,file1); + else + (void) rename(log,file1); + if (noaction) + printf("Start new log..."); + else { + fd = creat(log,perm); + if (fd < 0) { + perror("can't start new log"); + exit(1); + } + if (fchown(fd, owner_uid, group_gid)) { + perror("can't chmod new log file"); + exit(1); + } + (void) close(fd); + if (!(flags & CE_BINARY)) + if (log_trim(log)) { /* Add status message */ + perror("can't add status message to log"); + exit(1); + } + } + if (noaction) + printf("chmod %o %s...",perm,log); + else + (void) chmod(log,perm); + if (noaction) + printf("kill -HUP %d (syslogd)\n",syslog_pid); + else + if (kill(syslog_pid,SIGHUP)) { + fprintf(stderr,"%s: ",progname); + perror("warning - could not restart syslogd"); + } + if (flags & CE_COMPACT) { + if (noaction) + printf("Compress %s.0\n",log); + else + compress_log(log); + } +} + +/* Log the fact that the logs were turned over */ +log_trim(log) + char *log; +{ + FILE *f; + if ((f = fopen(log,"a")) == NULL) + return(-1); + fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n", + daytime, hostname, getpid()); + if (fclose(f) == EOF) { + perror("log_trim: fclose:"); + exit(1); + } + return(0); +} + +/* Fork of /usr/ucb/compress to compress the old log file */ +compress_log(log) + char *log; +{ + int pid; + char tmp[128]; + + pid = fork(); + (void) sprintf(tmp,"%s.0",log); + if (pid < 0) { + fprintf(stderr,"%s: ",progname); + perror("fork"); + exit(1); + } else if (!pid) { + (void) execl(COMPRESS,"compress","-f",tmp,0); + fprintf(stderr,"%s: ",progname); + perror(COMPRESS); + exit(1); + } +} + +/* Return size in kilobytes of a file */ +int sizefile(file) + char *file; +{ + struct stat sb; + + if (stat(file,&sb) < 0) + return(-1); + return(kbytes(dbtob(sb.st_blocks))); +} + +/* Return the age of old log file (file.0) */ +int age_old_log(file) + char *file; +{ + struct stat sb; + char tmp[80]; + + (void) strcpy(tmp,file); + if (stat(strcat(tmp,".0"),&sb) < 0) + if (stat(strcat(tmp,".Z"), &sb) < 0) + return(-1); + return( (int) (timenow - sb.st_mtime + 1800) / 3600); +} + + +#ifndef OSF +/* Duplicate a string using malloc */ + +char *strdup(strp) +register char *strp; +{ + register char *cp; + + if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL) + abort(); + return(strcpy (cp, strp)); +} +#endif + +/* Skip Over Blanks */ +char *sob(p) + register char *p; +{ + while (p && *p && isspace(*p)) + p++; + return(p); +} + +/* Skip Over Non-Blanks */ +char *son(p) + register char *p; +{ + while (p && *p && !isspace(*p)) + p++; + return(p); +} + + +/* Check if string is actually a number */ + +isnumber(string) +char *string; +{ + while (*string != '\0') { + if (*string < '0' || *string > '9') return(0); + string++; + } + return(1); +} + +#if !defined(__SABER__) && !defined(lint) && !defined(NO_WHAT_STRINGS) +static char *what_string = "@(#)newsyslog.c\t$Revision: 1.1 $ $Date: 1993/05/21 14:44:02 $ $Locker: $"; +#endif + +/* + * HISTORY + * $Log: newsyslog.c,v $ + * Revision 1.1 1993/05/21 14:44:02 cgd + * initial import of this log-rotation program to NetBSD + * + * Revision 3.0.2.2 1993/02/06 04:14:51 brezak + * Fix up rcsid. + * [1993/02/06 04:14:03 brezak] + * + * Revision 3.0 1993/01/01 07:39:17 ede + * Initial revision for OSF/1 R1.3 + * + * Revision 1.2 1991/08/16 09:50:26 devrcs + * From John Brezak, brezak@apollo.com, originally from Project Athena + * [91/07/24 09:33:18 meissner] + * + * $EndLog$ + */