newsyslog.c revision 16240
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: /usr/cvs/src/usr.sbin/newsyslog/newsyslog.c,v $
28 *      $Author: alex $
29 */
30
31#ifndef lint
32static char rcsid[] = "$Id: newsyslog.c,v 1.4 1996/06/07 16:27:28 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 (noaction)
424                printf("mv %s to %s\n",log,file1);
425        else
426                (void) rename(log,file1);
427        if (noaction)
428                printf("Start new log...");
429        else {
430                fd = creat(log,perm);
431                if (fd < 0)
432                        err(1, "can't start new log");
433                if (fchown(fd, owner_uid, group_gid))
434                        err(1, "can't chmod new log file");
435                (void) close(fd);
436                if (!(flags & CE_BINARY))
437                        if (log_trim(log))    /* Add status message */
438                             err(1, "can't add status message to log");
439        }
440        if (noaction)
441                printf("chmod %o %s...",perm,log);
442        else
443                (void) chmod(log,perm);
444        if (noaction)
445                printf("kill -HUP %d (syslogd)\n",syslog_pid);
446        else
447	if (syslog_pid < MIN_PID || syslog_pid > MAX_PID) {
448		warnx("preposterous process number: %d", syslog_pid);
449        } else if (kill(syslog_pid,SIGHUP))
450                warn("could not restart syslogd");
451        if (flags & CE_COMPACT) {
452                if (noaction)
453                        printf("Compress %s.0\n",log);
454                else
455                        compress_log(log);
456        }
457}
458
459/* Log the fact that the logs were turned over */
460static int log_trim(log)
461        char    *log;
462{
463        FILE    *f;
464        if ((f = fopen(log,"a")) == NULL)
465                return(-1);
466        fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
467                daytime, hostname, (int)getpid());
468        if (fclose(f) == EOF)
469                err(1, "log_trim: fclose:");
470        return(0);
471}
472
473/* Fork of /usr/ucb/compress to compress the old log file */
474static void compress_log(log)
475        char    *log;
476{
477        int     pid;
478        char    tmp[128];
479
480        pid = fork();
481        (void) sprintf(tmp,"%s.0",log);
482        if (pid < 0)
483                err(1, "fork");
484        else if (!pid) {
485                (void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0);
486                err(1, COMPRESS_PATH);
487        }
488}
489
490/* Return size in kilobytes of a file */
491static int sizefile(file)
492        char    *file;
493{
494        struct stat sb;
495
496        if (stat(file,&sb) < 0)
497                return(-1);
498        return(kbytes(dbtob(sb.st_blocks)));
499}
500
501/* Return the age of old log file (file.0) */
502static int age_old_log(file)
503        char    *file;
504{
505        struct stat sb;
506        char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1];
507
508        (void) strcpy(tmp,file);
509        if (stat(strcat(tmp,".0"),&sb) < 0)
510            if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
511                return(-1);
512        return( (int) (timenow - sb.st_mtime + 1800) / 3600);
513}
514
515
516#ifndef OSF
517/* Duplicate a string using malloc */
518
519char *strdup(strp)
520register char   *strp;
521{
522        register char *cp;
523
524        if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
525                abort();
526        return(strcpy (cp, strp));
527}
528#endif
529
530/* Skip Over Blanks */
531char *sob(p)
532        register char   *p;
533{
534        while (p && *p && isspace(*p))
535                p++;
536        return(p);
537}
538
539/* Skip Over Non-Blanks */
540char *son(p)
541        register char   *p;
542{
543        while (p && *p && !isspace(*p))
544                p++;
545        return(p);
546}
547