summaryrefslogtreecommitdiffstats
path: root/usr.sbin/newsyslog
diff options
context:
space:
mode:
authorgraichen <graichen@FreeBSD.org>1996-01-05 09:28:11 +0000
committergraichen <graichen@FreeBSD.org>1996-01-05 09:28:11 +0000
commitadc7000016eb37741d426b9e5362a1894b12093a (patch)
tree00ae2c080afb4e6b1dc075c44cddf172a65ab68c /usr.sbin/newsyslog
parentd35236a7fccc5eb4542d84a01d0b07f2bf8ba5c1 (diff)
downloadFreeBSD-src-adc7000016eb37741d426b9e5362a1894b12093a.zip
FreeBSD-src-adc7000016eb37741d426b9e5362a1894b12093a.tar.gz
Obtained from: NetBSD
imported the newsyslog command from NetBSD - it make the "rotation" of the logfiles much simpler (it is currently done by "hand" in the /etc/[daily,weekly,monthly] scripts) - now it will be done by invoking newsyslog every hour which is very customizable via a /etc/newsyslog.conf file
Diffstat (limited to 'usr.sbin/newsyslog')
-rw-r--r--usr.sbin/newsyslog/Makefile15
-rw-r--r--usr.sbin/newsyslog/newsyslog.8168
-rw-r--r--usr.sbin/newsyslog/newsyslog.c568
3 files changed, 751 insertions, 0 deletions
diff --git a/usr.sbin/newsyslog/Makefile b/usr.sbin/newsyslog/Makefile
new file mode 100644
index 0000000..c04af65
--- /dev/null
+++ b/usr.sbin/newsyslog/Makefile
@@ -0,0 +1,15 @@
+# $Id: Makefile,v 1.6 1994/12/22 12:30:26 cgd Exp $
+
+PROG= newsyslog
+
+CFLAGS+= -DOSF
+CFLAGS+= -DCONF=\"/etc/newsyslog.conf\"
+CFLAGS+= -DPIDFILE=\"/var/run/syslog.pid\"
+CFLAGS+= -DCOMPRESS=\"/usr/bin/gzip\"
+CFLAGS+= -DCOMPRESS_POSTFIX=\".gz\"
+
+BINOWN= root
+
+MAN8= newsyslog.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/newsyslog/newsyslog.8 b/usr.sbin/newsyslog/newsyslog.8
new file mode 100644
index 0000000..72e4816
--- /dev/null
+++ b/usr.sbin/newsyslog/newsyslog.8
@@ -0,0 +1,168 @@
+.TH NEWSYSLOG 8 "January 12, 1989" "Project Athena"
+.ns
+.\" This file contains changes from the Open Software Foundation.
+.\"
+.\" from: @(#)newsyslog.8
+.\" $Id: newsyslog.8,v 1.6 1995/01/06 19:20:20 jtc Exp $
+.\"
+.\" Copyright 1988, 1989 by the Massachusetts Institute of Technology
+.\"
+.\" 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 names of M.I.T. and the M.I.T. S.I.P.B. not be
+.\" used in advertising or publicity pertaining to distribution
+.\" of the software without specific, written prior permission.
+.\" M.I.T. and the M.I.T. S.I.P.B. make no representations about
+.\" the suitability of this software for any purpose. It is
+.\" provided "as is" without express or implied warranty.
+.\"
+.sp
+.SH NAME
+newsyslog \- maintain system log files to manageable sizes
+.SH SYNOPSIS
+.B /usr/bin/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 /etc/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 ``*'', then 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/bin/gzip. 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 /etc/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
+/etc/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), gzip(1)
+.ns
+.sp
diff --git a/usr.sbin/newsyslog/newsyslog.c b/usr.sbin/newsyslog/newsyslog.c
new file mode 100644
index 0000000..ba21b6e
--- /dev/null
+++ b/usr.sbin/newsyslog/newsyslog.c
@@ -0,0 +1,568 @@
+/*
+ * This file contains changes from the Open Software Foundation.
+ */
+
+/*
+
+Copyright 1988, 1989 by the Massachusetts Institute of Technology
+
+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 names of M.I.T. and the M.I.T. S.I.P.B. not be
+used in advertising or publicity pertaining to distribution
+of the software without specific, written prior permission.
+M.I.T. and the M.I.T. S.I.P.B. make no representations about
+the suitability of this software for any purpose. It is
+provided "as is" without express or implied warranty.
+
+*/
+
+/*
+ * newsyslog - roll over selected logs at the appropriate time,
+ * keeping the a specified number of backup files around.
+ *
+ * $Source: /a/cvsroot/src/usr.bin/newsyslog/newsyslog.c,v $
+ * $Author: jtc $
+ */
+
+#ifndef lint
+static char rcsid[] = "$Id: newsyslog.c,v 1.9 1995/01/21 21:53:46 jtc Exp $";
+#endif /* not lint */
+
+#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
+#ifndef COMPRESS_POSTFIX
+#define COMPRESS_POSTFIX ".Z"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/wait.h>
+
+#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 */
+#define MIN_PID 3
+#define MAX_PID 65534
+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];
+ char *p;
+
+ progname = argv[0];
+ timenow = time((time_t *) 0);
+ daytime = ctime(&timenow) + 4;
+ daytime[16] = '\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);
+ if (f)
+ (void)fclose(f);
+
+ /* Let's get our hostname */
+ (void) gethostname(hostname, sizeof(hostname));
+
+ /* Truncate domain */
+ if (p = strchr(hostname, '.')) {
+ *p = '\0';
+ }
+
+ 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", progname);
+ 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 = strchr(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, COMPRESS_POSTFIX);
+
+ 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, COMPRESS_POSTFIX);
+ (void) strcat(zfile2, COMPRESS_POSTFIX);
+ 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 (syslog_pid < MIN_PID || syslog_pid > MAX_PID) {
+ fprintf(stderr,"%s: preposterous process number: %d\n",
+ progname, 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[MAXPATHLEN+3];
+
+ (void) strcpy(tmp,file);
+ if (stat(strcat(tmp,".0"),&sb) < 0)
+ if (stat(strcat(tmp,COMPRESS_POSTFIX), &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);
+}
OpenPOWER on IntegriCloud