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