newsyslog.c revision 18075
1/*
2 * This file contains changes from the Open Software Foundation.
3 */
4
5/*
6
7Copyright 1988, 1989 by the Massachusetts Institute of Technology
8
9Permission to use, copy, modify, and distribute this software
10and its documentation for any purpose and without fee is
11hereby granted, provided that the above copyright notice
12appear in all copies and that both that copyright notice and
13this permission notice appear in supporting documentation,
14and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
15used in advertising or publicity pertaining to distribution
16of the software without specific, written prior permission.
17M.I.T. and the M.I.T. S.I.P.B. make no representations about
18the suitability of this software for any purpose.  It is
19provided "as is" without express or implied warranty.
20
21*/
22
23/*
24 *      newsyslog - roll over selected logs at the appropriate time,
25 *              keeping the a specified number of backup files around.
26 *
27 *      $Source: /home/ncvs/src/usr.sbin/newsyslog/newsyslog.c,v $
28 *      $Author: alex $
29 */
30
31#ifndef lint
32static char rcsid[] = "$Id: newsyslog.c,v 1.5 1996/06/08 23:32:10 alex Exp $";
33#endif /* not lint */
34
35#ifndef CONF
36#define CONF "/etc/athena/newsyslog.conf" /* Configuration file */
37#endif
38#ifndef PIDFILE
39#define PIDFILE "/etc/syslog.pid"
40#endif
41#ifndef COMPRESS_PATH
42#define COMPRESS_PATH "/usr/ucb/compress" /* File compression program */
43#endif
44#ifndef COMPRESS_PROG
45#define COMPRESS_PROG "compress"
46#endif
47#ifndef COMPRESS_POSTFIX
48#define COMPRESS_POSTFIX ".Z"
49#endif
50
51#include <stdio.h>
52#include <stdlib.h>
53#include <string.h>
54#include <ctype.h>
55#include <signal.h>
56#include <pwd.h>
57#include <grp.h>
58#include <fcntl.h>
59#include <unistd.h>
60#include <err.h>
61#include <sys/types.h>
62#include <sys/time.h>
63#include <sys/stat.h>
64#include <sys/param.h>
65#include <sys/wait.h>
66
67#define kbytes(size)  (((size) + 1023) >> 10)
68#ifdef _IBMR2
69/* Calculates (db * DEV_BSIZE) */
70#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
71#endif
72
73#define CE_COMPACT 1            /* Compact the achived log files */
74#define CE_BINARY  2            /* Logfile is in binary, don't add */
75                                /* status messages */
76#define NONE -1
77
78struct conf_entry {
79        char    *log;           /* Name of the log */
80        int     uid;            /* Owner of log */
81        int     gid;            /* Group of log */
82        int     numlogs;        /* Number of logs to keep */
83        int     size;           /* Size cutoff to trigger trimming the log */
84        int     hours;          /* Hours between log trimming */
85        int     permissions;    /* File permissions on the log */
86        int     flags;          /* Flags (CE_COMPACT & CE_BINARY)  */
87        struct conf_entry       *next; /* Linked list pointer */
88};
89
90char    *progname;              /* contains argv[0] */
91int     verbose = 0;            /* Print out what's going on */
92int     needroot = 1;           /* Root privs are necessary */
93int     noaction = 0;           /* Don't do anything, just show it */
94char    *conf = CONF;           /* Configuration file to use */
95time_t  timenow;
96int     syslog_pid;             /* read in from /etc/syslog.pid */
97#define MIN_PID		3
98#define MAX_PID		30000   /* was 65534, see /usr/include/sys/proc.h */
99char    hostname[MAXHOSTNAMELEN+1]; /* hostname */
100char    *daytime;               /* timenow in human readable form */
101
102#ifndef OSF
103char *strdup(char *strp);
104#endif
105
106static struct conf_entry *parse_file();
107static char *sob(char *p);
108static char *son(char *p);
109static char *missing_field(char *p,char *errline);
110static void do_entry(struct conf_entry *ent);
111static void PRS(int argc,char **argv);
112static void usage();
113static void dotrim(char *log,int numdays,int falgs,int perm, int owner_uid,int group_gid);
114static int log_trim(char *log);
115static void compress_log(char *log);
116static int sizefile(char *file);
117static int age_old_log(char *file);
118
119int main(argc,argv)
120        int argc;
121        char **argv;
122{
123        struct conf_entry *p, *q;
124
125        PRS(argc,argv);
126        if (needroot && getuid() && geteuid()) {
127                fprintf(stderr,"%s: must have root privs\n",progname);
128                return(1);
129        }
130        p = q = parse_file();
131        while (p) {
132                do_entry(p);
133                p=p->next;
134                free((char *) q);
135                q=p;
136        }
137        return(0);
138}
139
140static void do_entry(ent)
141        struct conf_entry       *ent;
142
143{
144        int     size, modtime;
145
146        if (verbose) {
147                if (ent->flags & CE_COMPACT)
148                        printf("%s <%dZ>: ",ent->log,ent->numlogs);
149                else
150                        printf("%s <%d>: ",ent->log,ent->numlogs);
151        }
152        size = sizefile(ent->log);
153        modtime = age_old_log(ent->log);
154        if (size < 0) {
155                if (verbose)
156                        printf("does not exist.\n");
157        } else {
158                if (verbose && (ent->size > 0))
159                        printf("size (Kb): %d [%d] ", size, ent->size);
160                if (verbose && (ent->hours > 0))
161                        printf(" age (hr): %d [%d] ", modtime, ent->hours);
162                if (((ent->size > 0) && (size >= ent->size)) ||
163                    ((ent->hours > 0) && ((modtime >= ent->hours)
164                                        || (modtime < 0)))) {
165                        if (verbose)
166                                printf("--> trimming log....\n");
167                        if (noaction && !verbose) {
168                                if (ent->flags & CE_COMPACT)
169                                        printf("%s <%dZ>: trimming",
170                                               ent->log,ent->numlogs);
171                                else
172                                        printf("%s <%d>: trimming",
173                                               ent->log,ent->numlogs);
174                        }
175                        dotrim(ent->log, ent->numlogs, ent->flags,
176                               ent->permissions, ent->uid, ent->gid);
177                } else {
178                        if (verbose)
179                                printf("--> skipping\n");
180                }
181        }
182}
183
184static void PRS(argc,argv)
185        int argc;
186        char **argv;
187{
188        int     c;
189        FILE    *f;
190        char    line[BUFSIZ];
191	char	*p;
192
193        progname = argv[0];
194        timenow = time((time_t *) 0);
195        daytime = ctime(&timenow) + 4;
196        daytime[15] = '\0';
197
198        /* Let's find the pid of syslogd */
199        syslog_pid = 0;
200        f = fopen(PIDFILE,"r");
201        if (f && fgets(line,BUFSIZ,f))
202                syslog_pid = atoi(line);
203	if (f)
204		(void)fclose(f);
205
206        /* Let's get our hostname */
207        (void) gethostname(hostname, sizeof(hostname));
208
209	/* Truncate domain */
210	if ((p = strchr(hostname, '.'))) {
211		*p = '\0';
212	}
213
214        optind = 1;             /* Start options parsing */
215        while ((c=getopt(argc,argv,"nrvf:t:")) != EOF)
216                switch (c) {
217                case 'n':
218                        noaction++; /* This implies needroot as off */
219                        /* fall through */
220                case 'r':
221                        needroot = 0;
222                        break;
223                case 'v':
224                        verbose++;
225                        break;
226                case 'f':
227                        conf = optarg;
228                        break;
229                default:
230                        usage();
231                }
232        }
233
234static void usage()
235{
236        fprintf(stderr,
237                "Usage: %s <-nrv> <-f config-file>\n", progname);
238        exit(1);
239}
240
241/* Parse a configuration file and return a linked list of all the logs
242 * to process
243 */
244static struct conf_entry *parse_file()
245{
246        FILE    *f;
247        char    line[BUFSIZ], *parse, *q;
248        char    *errline, *group;
249        struct conf_entry *first = NULL;
250        struct conf_entry *working = NULL;
251        struct passwd *pass;
252        struct group *grp;
253
254        if (strcmp(conf,"-"))
255                f = fopen(conf,"r");
256        else
257                f = stdin;
258        if (!f)
259                err(1, "%s", conf);
260        while (fgets(line,BUFSIZ,f)) {
261                if ((line[0]== '\n') || (line[0] == '#'))
262                        continue;
263                errline = strdup(line);
264                if (!first) {
265                        working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
266                        first = working;
267                } else {
268                        working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
269                        working = working->next;
270                }
271
272                q = parse = missing_field(sob(line),errline);
273                *(parse = son(line)) = '\0';
274                working->log = strdup(q);
275
276                q = parse = missing_field(sob(++parse),errline);
277                *(parse = son(parse)) = '\0';
278                if ((group = strchr(q, '.')) != NULL) {
279                    *group++ = '\0';
280                    if (*q) {
281                        if (!(isnumber(*q))) {
282                            if ((pass = getpwnam(q)) == NULL)
283                                errx(1,
284                                  "Error in config file; unknown user:\n%s",
285                                  errline);
286                            working->uid = pass->pw_uid;
287                        } else
288                            working->uid = atoi(q);
289                    } else
290                        working->uid = NONE;
291
292                    q = group;
293                    if (*q) {
294                        if (!(isnumber(*q))) {
295                            if ((grp = getgrnam(q)) == NULL)
296                                errx(1,
297                                  "Error in config file; unknown group:\n%s",
298                                  errline);
299                            working->gid = grp->gr_gid;
300                        } else
301                            working->gid = atoi(q);
302                    } else
303                        working->gid = NONE;
304
305                    q = parse = missing_field(sob(++parse),errline);
306                    *(parse = son(parse)) = '\0';
307                }
308                else
309                    working->uid = working->gid = NONE;
310
311                if (!sscanf(q,"%o",&working->permissions))
312                        errx(1, "Error in config file; bad permissions:\n%s",
313                          errline);
314
315                q = parse = missing_field(sob(++parse),errline);
316                *(parse = son(parse)) = '\0';
317                if (!sscanf(q,"%d",&working->numlogs))
318                        errx(1, "Error in config file; bad number:\n%s",
319                          errline);
320
321                q = parse = missing_field(sob(++parse),errline);
322                *(parse = son(parse)) = '\0';
323                if (isdigit(*q))
324                        working->size = atoi(q);
325                else
326                        working->size = -1;
327
328                q = parse = missing_field(sob(++parse),errline);
329                *(parse = son(parse)) = '\0';
330                if (isdigit(*q))
331                        working->hours = atoi(q);
332                else
333                        working->hours = -1;
334
335                q = parse = sob(++parse); /* Optional field */
336                *(parse = son(parse)) = '\0';
337                working->flags = 0;
338                while (q && *q && !isspace(*q)) {
339                        if ((*q == 'Z') || (*q == 'z'))
340                                working->flags |= CE_COMPACT;
341                        else if ((*q == 'B') || (*q == 'b'))
342                                working->flags |= CE_BINARY;
343                        else
344                           errx(1, "Illegal flag in config file -- %c", *q);
345                        q++;
346                }
347
348                free(errline);
349        }
350        if (working)
351                working->next = (struct conf_entry *) NULL;
352        (void) fclose(f);
353        return(first);
354}
355
356static char *missing_field(p,errline)
357        char    *p,*errline;
358{
359        if (!p || !*p)
360            errx(1, "Missing field in config file:\n%s", errline);
361        return(p);
362}
363
364static void dotrim(log,numdays,flags,perm,owner_uid,group_gid)
365        char    *log;
366        int     numdays;
367        int     flags;
368        int     perm;
369        int     owner_uid;
370        int     group_gid;
371{
372        char    file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1];
373        char    zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1];
374        int     fd;
375        struct  stat st;
376
377#ifdef _IBMR2
378/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
379/* change it to be owned by uid -1, instead of leaving it as is, as it is */
380/* supposed to. */
381                if (owner_uid == -1)
382                  owner_uid = geteuid();
383#endif
384
385        /* Remove oldest log */
386        (void) sprintf(file1,"%s.%d",log,numdays);
387        (void) strcpy(zfile1, file1);
388        (void) strcat(zfile1, COMPRESS_POSTFIX);
389
390        if (noaction) {
391                printf("rm -f %s\n", file1);
392                printf("rm -f %s\n", zfile1);
393        } else {
394                (void) unlink(file1);
395                (void) unlink(zfile1);
396        }
397
398        /* Move down log files */
399        while (numdays--) {
400                (void) strcpy(file2,file1);
401                (void) sprintf(file1,"%s.%d",log,numdays);
402                (void) strcpy(zfile1, file1);
403                (void) strcpy(zfile2, file2);
404                if (lstat(file1, &st)) {
405                        (void) strcat(zfile1, COMPRESS_POSTFIX);
406                        (void) strcat(zfile2, COMPRESS_POSTFIX);
407                        if (lstat(zfile1, &st)) continue;
408                }
409                if (noaction) {
410                        printf("mv %s %s\n",zfile1,zfile2);
411                        printf("chmod %o %s\n", perm, zfile2);
412                        printf("chown %d.%d %s\n",
413                               owner_uid, group_gid, zfile2);
414                } else {
415                        (void) rename(zfile1, zfile2);
416                        (void) chmod(zfile2, perm);
417                        (void) chown(zfile2, owner_uid, group_gid);
418                }
419        }
420        if (!noaction && !(flags & CE_BINARY))
421                (void) log_trim(log);  /* Report the trimming to the old log */
422
423	if (numdays == -1) {
424		if (noaction)
425			printf("rm %s\n",log);
426		else
427			(void)unlink(log);
428	}
429	else {
430		if (noaction)
431			printf("mv %s to %s\n",log,file1);
432		else
433			(void)rename(log, file1);
434	}
435
436        if (noaction)
437                printf("Start new log...");
438        else {
439                fd = creat(log,perm);
440                if (fd < 0)
441                        err(1, "can't start new log");
442                if (fchown(fd, owner_uid, group_gid))
443                        err(1, "can't chmod new log file");
444                (void) close(fd);
445                if (!(flags & CE_BINARY))
446                        if (log_trim(log))    /* Add status message */
447                             err(1, "can't add status message to log");
448        }
449        if (noaction)
450                printf("chmod %o %s...",perm,log);
451        else
452                (void) chmod(log,perm);
453        if (noaction)
454                printf("kill -HUP %d (syslogd)\n",syslog_pid);
455        else
456	if (syslog_pid < MIN_PID || syslog_pid > MAX_PID) {
457		warnx("preposterous process number: %d", syslog_pid);
458        } else if (kill(syslog_pid,SIGHUP))
459                warn("could not restart syslogd");
460        if (flags & CE_COMPACT) {
461                if (noaction)
462                        printf("Compress %s.0\n",log);
463                else
464                        compress_log(log);
465        }
466}
467
468/* Log the fact that the logs were turned over */
469static int log_trim(log)
470        char    *log;
471{
472        FILE    *f;
473        if ((f = fopen(log,"a")) == NULL)
474                return(-1);
475        fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
476                daytime, hostname, (int)getpid());
477        if (fclose(f) == EOF)
478                err(1, "log_trim: fclose:");
479        return(0);
480}
481
482/* Fork of /usr/ucb/compress to compress the old log file */
483static void compress_log(log)
484        char    *log;
485{
486        int     pid;
487        char    tmp[128];
488
489        pid = fork();
490        (void) sprintf(tmp,"%s.0",log);
491        if (pid < 0)
492                err(1, "fork");
493        else if (!pid) {
494                (void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0);
495                err(1, COMPRESS_PATH);
496        }
497}
498
499/* Return size in kilobytes of a file */
500static int sizefile(file)
501        char    *file;
502{
503        struct stat sb;
504
505        if (stat(file,&sb) < 0)
506                return(-1);
507        return(kbytes(dbtob(sb.st_blocks)));
508}
509
510/* Return the age of old log file (file.0) */
511static int age_old_log(file)
512        char    *file;
513{
514        struct stat sb;
515        char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1];
516
517        (void) strcpy(tmp,file);
518        if (stat(strcat(tmp,".0"),&sb) < 0)
519            if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
520                return(-1);
521        return( (int) (timenow - sb.st_mtime + 1800) / 3600);
522}
523
524
525#ifndef OSF
526/* Duplicate a string using malloc */
527
528char *strdup(strp)
529register char   *strp;
530{
531        register char *cp;
532
533        if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
534                abort();
535        return(strcpy (cp, strp));
536}
537#endif
538
539/* Skip Over Blanks */
540char *sob(p)
541        register char   *p;
542{
543        while (p && *p && isspace(*p))
544                p++;
545        return(p);
546}
547
548/* Skip Over Non-Blanks */
549char *son(p)
550        register char   *p;
551{
552        while (p && *p && !isspace(*p))
553                p++;
554        return(p);
555}
556