newsyslog.c revision 25496
174462Salfred/*
274462Salfred * This file contains changes from the Open Software Foundation.
374462Salfred */
474462Salfred
574462Salfred/*
674462Salfred
774462SalfredCopyright 1988, 1989 by the Massachusetts Institute of Technology
874462Salfred
974462SalfredPermission to use, copy, modify, and distribute this software
1074462Salfredand its documentation for any purpose and without fee is
1174462Salfredhereby granted, provided that the above copyright notice
1274462Salfredappear in all copies and that both that copyright notice and
1374462Salfredthis permission notice appear in supporting documentation,
1474462Salfredand that the names of M.I.T. and the M.I.T. S.I.P.B. not be
1574462Salfredused in advertising or publicity pertaining to distribution
1674462Salfredof the software without specific, written prior permission.
1774462SalfredM.I.T. and the M.I.T. S.I.P.B. make no representations about
1874462Salfredthe suitability of this software for any purpose.  It is
1974462Salfredprovided "as is" without express or implied warranty.
2074462Salfred
2174462Salfred*/
2274462Salfred
2374462Salfred/*
2474462Salfred *      newsyslog - roll over selected logs at the appropriate time,
2574462Salfred *              keeping the a specified number of backup files around.
2674462Salfred *
2774462Salfred *      $Source: /home/ncvs/src/usr.sbin/newsyslog/newsyslog.c,v $
2874462Salfred *      $Author: ache $
2974462Salfred */
3074462Salfred
3174462Salfred#ifndef lint
3274462Salfredstatic char rcsid[] = "$Id: newsyslog.c,v 1.11 1997/05/04 01:53:53 ache Exp $";
3374462Salfred#endif /* not lint */
3474462Salfred
3574462Salfred#ifndef CONF
3674462Salfred#define CONF "/etc/athena/newsyslog.conf" /* Configuration file */
3774462Salfred#endif
3874462Salfred#ifndef PIDFILE
3974462Salfred#define PIDFILE "/etc/syslog.pid"
4074462Salfred#endif
4174462Salfred#ifndef COMPRESS_PATH
4274462Salfred#define COMPRESS_PATH "/usr/ucb/compress" /* File compression program */
4374462Salfred#endif
4474462Salfred#ifndef COMPRESS_PROG
4574462Salfred#define COMPRESS_PROG "compress"
4674462Salfred#endif
4774462Salfred#ifndef COMPRESS_POSTFIX
4874462Salfred#define COMPRESS_POSTFIX ".Z"
4974462Salfred#endif
5074462Salfred
5174462Salfred#include <stdio.h>
5274462Salfred#include <stdlib.h>
5374462Salfred#include <string.h>
5474462Salfred#include <ctype.h>
5574462Salfred#include <signal.h>
5674462Salfred#include <pwd.h>
5774462Salfred#include <grp.h>
5874462Salfred#include <fcntl.h>
5974462Salfred#include <unistd.h>
6074462Salfred#include <err.h>
6174462Salfred#include <sys/types.h>
6274462Salfred#include <sys/time.h>
6374462Salfred#include <sys/stat.h>
6474462Salfred#include <sys/param.h>
6574462Salfred#include <sys/wait.h>
6674462Salfred
6774462Salfred#define kbytes(size)  (((size) + 1023) >> 10)
6874462Salfred#ifdef _IBMR2
6974462Salfred/* Calculates (db * DEV_BSIZE) */
7074462Salfred#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
7174462Salfred#endif
7274462Salfred
7374462Salfred#define CE_COMPACT 1            /* Compact the achived log files */
7474462Salfred#define CE_BINARY  2            /* Logfile is in binary, don't add */
7574462Salfred                                /* status messages */
7674462Salfred#define NONE -1
7774462Salfred
7874462Salfredstruct conf_entry {
7974462Salfred        char    *log;           /* Name of the log */
8074462Salfred	char	*pid_file;	/* PID file */
8174462Salfred        int     uid;            /* Owner of log */
8274462Salfred        int     gid;            /* Group of log */
8374462Salfred        int     numlogs;        /* Number of logs to keep */
8474462Salfred        int     size;           /* Size cutoff to trigger trimming the log */
8574462Salfred        int     hours;          /* Hours between log trimming */
8674462Salfred        int     permissions;    /* File permissions on the log */
8774462Salfred        int     flags;          /* Flags (CE_COMPACT & CE_BINARY)  */
8874462Salfred        struct conf_entry       *next; /* Linked list pointer */
8974462Salfred};
90126243Sgreen
9174462Salfredchar    *progname;              /* contains argv[0] */
9274462Salfredint     verbose = 0;            /* Print out what's going on */
9374462Salfredint     needroot = 1;           /* Root privs are necessary */
9474462Salfredint     noaction = 0;           /* Don't do anything, just show it */
95126243Sgreenchar    *conf = CONF;           /* Configuration file to use */
9674462Salfredtime_t  timenow;
9774462Salfredpid_t   syslog_pid;             /* read in from /etc/syslog.pid */
9874462Salfred#define MIN_PID         5
9974462Salfred#define MAX_PID		30000   /* was 65534, see /usr/include/sys/proc.h */
10074462Salfredchar    hostname[MAXHOSTNAMELEN+1]; /* hostname */
10174462Salfredchar    *daytime;               /* timenow in human readable form */
10274462Salfred
10374462Salfred#ifndef OSF
10474462Salfredchar *strdup(char *strp);
10574462Salfred#endif
10674462Salfred
107106288Sdfrstatic struct conf_entry *parse_file();
108106288Sdfrstatic char *sob(char *p);
10974462Salfredstatic char *son(char *p);
11074462Salfredstatic char *missing_field(char *p,char *errline);
11174462Salfredstatic void do_entry(struct conf_entry *ent);
11274462Salfredstatic void PRS(int argc,char **argv);
11374462Salfredstatic void usage();
11474462Salfredstatic void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm, int owner_uid,int group_gid);
11574462Salfredstatic int log_trim(char *log);
11674462Salfredstatic void compress_log(char *log);
11774462Salfredstatic int sizefile(char *file);
11874462Salfredstatic int age_old_log(char *file);
11974462Salfredstatic pid_t get_pid(char *pid_file);
12074462Salfred
12174462Salfredint main(argc,argv)
12274462Salfred        int argc;
12374462Salfred        char **argv;
12474462Salfred{
125126243Sgreen        struct conf_entry *p, *q;
12674462Salfred
12774462Salfred        PRS(argc,argv);
12874462Salfred        if (needroot && getuid() && geteuid()) {
129                fprintf(stderr,"%s: must have root privs\n",progname);
130                return(1);
131        }
132        p = q = parse_file();
133
134	syslog_pid = needroot ? get_pid(PIDFILE) : 0;
135
136        while (p) {
137                do_entry(p);
138                p=p->next;
139                free((char *) q);
140                q=p;
141        }
142        return(0);
143}
144
145static void do_entry(ent)
146        struct conf_entry       *ent;
147
148{
149        int     size, modtime;
150
151        if (verbose) {
152                if (ent->flags & CE_COMPACT)
153                        printf("%s <%dZ>: ",ent->log,ent->numlogs);
154                else
155                        printf("%s <%d>: ",ent->log,ent->numlogs);
156        }
157        size = sizefile(ent->log);
158        modtime = age_old_log(ent->log);
159        if (size < 0) {
160                if (verbose)
161                        printf("does not exist.\n");
162        } else {
163                if (verbose && (ent->size > 0))
164                        printf("size (Kb): %d [%d] ", size, ent->size);
165                if (verbose && (ent->hours > 0))
166                        printf(" age (hr): %d [%d] ", modtime, ent->hours);
167                if (((ent->size > 0) && (size >= ent->size)) ||
168                    ((ent->hours > 0) && ((modtime >= ent->hours)
169                                        || (modtime < 0)))) {
170                        if (verbose)
171                                printf("--> trimming log....\n");
172                        if (noaction && !verbose) {
173                                if (ent->flags & CE_COMPACT)
174                                        printf("%s <%dZ>: trimming",
175                                               ent->log,ent->numlogs);
176                                else
177                                        printf("%s <%d>: trimming",
178                                               ent->log,ent->numlogs);
179                        }
180                        dotrim(ent->log, ent->pid_file, ent->numlogs,
181			    ent->flags, ent->permissions, ent->uid, ent->gid);
182                } else {
183                        if (verbose)
184                                printf("--> skipping\n");
185                }
186        }
187}
188
189static void PRS(argc,argv)
190        int argc;
191        char **argv;
192{
193        int     c;
194	char	*p;
195
196        progname = argv[0];
197        timenow = time((time_t *) 0);
198        daytime = ctime(&timenow) + 4;
199        daytime[15] = '\0';
200
201        /* Let's get our hostname */
202        (void) gethostname(hostname, sizeof(hostname));
203
204	/* Truncate domain */
205	if ((p = strchr(hostname, '.'))) {
206		*p = '\0';
207	}
208
209        optind = 1;             /* Start options parsing */
210        while ((c=getopt(argc,argv,"nrvf:t:")) != -1)
211                switch (c) {
212                case 'n':
213                        noaction++; /* This implies needroot as off */
214                        /* fall through */
215                case 'r':
216                        needroot = 0;
217                        break;
218                case 'v':
219                        verbose++;
220                        break;
221                case 'f':
222                        conf = optarg;
223                        break;
224                default:
225                        usage();
226                }
227        }
228
229static void usage()
230{
231        fprintf(stderr,
232                "Usage: %s <-nrv> <-f config-file>\n", progname);
233        exit(1);
234}
235
236/* Parse a configuration file and return a linked list of all the logs
237 * to process
238 */
239static struct conf_entry *parse_file()
240{
241        FILE    *f;
242        char    line[BUFSIZ], *parse, *q;
243        char    *errline, *group;
244        struct conf_entry *first = NULL;
245        struct conf_entry *working = NULL;
246        struct passwd *pass;
247        struct group *grp;
248
249        if (strcmp(conf,"-"))
250                f = fopen(conf,"r");
251        else
252                f = stdin;
253        if (!f)
254                err(1, "%s", conf);
255        while (fgets(line,BUFSIZ,f)) {
256                if ((line[0]== '\n') || (line[0] == '#'))
257                        continue;
258                errline = strdup(line);
259                if (!first) {
260                        working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
261                        first = working;
262                } else {
263                        working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
264                        working = working->next;
265                }
266
267                q = parse = missing_field(sob(line),errline);
268                *(parse = son(line)) = '\0';
269                working->log = strdup(q);
270
271                q = parse = missing_field(sob(++parse),errline);
272                *(parse = son(parse)) = '\0';
273                if ((group = strchr(q, '.')) != NULL) {
274                    *group++ = '\0';
275                    if (*q) {
276                        if (!(isnumber(*q))) {
277                            if ((pass = getpwnam(q)) == NULL)
278                                errx(1,
279                                  "Error in config file; unknown user:\n%s",
280                                  errline);
281                            working->uid = pass->pw_uid;
282                        } else
283                            working->uid = atoi(q);
284                    } else
285                        working->uid = NONE;
286
287                    q = group;
288                    if (*q) {
289                        if (!(isnumber(*q))) {
290                            if ((grp = getgrnam(q)) == NULL)
291                                errx(1,
292                                  "Error in config file; unknown group:\n%s",
293                                  errline);
294                            working->gid = grp->gr_gid;
295                        } else
296                            working->gid = atoi(q);
297                    } else
298                        working->gid = NONE;
299
300                    q = parse = missing_field(sob(++parse),errline);
301                    *(parse = son(parse)) = '\0';
302                }
303                else
304                    working->uid = working->gid = NONE;
305
306                if (!sscanf(q,"%o",&working->permissions))
307                        errx(1, "Error in config file; bad permissions:\n%s",
308                          errline);
309
310                q = parse = missing_field(sob(++parse),errline);
311                *(parse = son(parse)) = '\0';
312                if (!sscanf(q,"%d",&working->numlogs))
313                        errx(1, "Error in config file; bad number:\n%s",
314                          errline);
315
316                q = parse = missing_field(sob(++parse),errline);
317                *(parse = son(parse)) = '\0';
318                if (isdigit(*q))
319                        working->size = atoi(q);
320                else
321                        working->size = -1;
322
323                q = parse = missing_field(sob(++parse),errline);
324                *(parse = son(parse)) = '\0';
325                if (isdigit(*q))
326                        working->hours = atoi(q);
327                else
328                        working->hours = -1;
329
330                q = parse = sob(++parse); /* Optional field */
331                *(parse = son(parse)) = '\0';
332
333                working->flags = 0;
334                while (q && *q && !isspace(*q)) {
335                        if ((*q == 'Z') || (*q == 'z'))
336                                working->flags |= CE_COMPACT;
337                        else if ((*q == 'B') || (*q == 'b'))
338                                working->flags |= CE_BINARY;
339                        else
340                           errx(1, "Illegal flag in config file -- %c", *q);
341                        q++;
342                }
343
344		q = parse = sob(++parse); /* Optional field */
345		*(parse = son(parse)) = '\0';
346
347		working->pid_file = NULL;
348		if (q && *q) {
349			if (*q == '/')
350				working->pid_file = strdup(q);
351                        else
352			   errx(1, "Illegal pid file in config file:\n%s", q);
353		}
354
355                free(errline);
356        }
357        if (working)
358                working->next = (struct conf_entry *) NULL;
359        (void) fclose(f);
360        return(first);
361}
362
363static char *missing_field(p,errline)
364        char    *p,*errline;
365{
366        if (!p || !*p)
367            errx(1, "Missing field in config file:\n%s", errline);
368        return(p);
369}
370
371static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid)
372        char    *log;
373	char    *pid_file;
374        int     numdays;
375        int     flags;
376        int     perm;
377        int     owner_uid;
378        int     group_gid;
379{
380        char    file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1];
381        char    zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1];
382	int     notified, need_notification, fd, _numdays;
383        struct  stat st;
384	pid_t   pid;
385
386#ifdef _IBMR2
387/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
388/* change it to be owned by uid -1, instead of leaving it as is, as it is */
389/* supposed to. */
390                if (owner_uid == -1)
391                  owner_uid = geteuid();
392#endif
393
394        /* Remove oldest log */
395        (void) sprintf(file1,"%s.%d",log,numdays);
396        (void) strcpy(zfile1, file1);
397        (void) strcat(zfile1, COMPRESS_POSTFIX);
398
399        if (noaction) {
400                printf("rm -f %s\n", file1);
401                printf("rm -f %s\n", zfile1);
402        } else {
403                (void) unlink(file1);
404                (void) unlink(zfile1);
405        }
406
407        /* Move down log files */
408	_numdays = numdays;	/* preserve */
409        while (numdays--) {
410                (void) strcpy(file2,file1);
411                (void) sprintf(file1,"%s.%d",log,numdays);
412                (void) strcpy(zfile1, file1);
413                (void) strcpy(zfile2, file2);
414                if (lstat(file1, &st)) {
415                        (void) strcat(zfile1, COMPRESS_POSTFIX);
416                        (void) strcat(zfile2, COMPRESS_POSTFIX);
417			if (lstat(zfile1, &st)) continue;
418                }
419                if (noaction) {
420                        printf("mv %s %s\n",zfile1,zfile2);
421                        printf("chmod %o %s\n", perm, zfile2);
422                        printf("chown %d.%d %s\n",
423                               owner_uid, group_gid, zfile2);
424                } else {
425                        (void) rename(zfile1, zfile2);
426                        (void) chmod(zfile2, perm);
427                        (void) chown(zfile2, owner_uid, group_gid);
428                }
429        }
430        if (!noaction && !(flags & CE_BINARY))
431                (void) log_trim(log);  /* Report the trimming to the old log */
432
433	if (!_numdays) {
434		if (noaction)
435			printf("rm %s\n",log);
436		else
437			(void)unlink(log);
438	}
439	else {
440		if (noaction)
441			printf("mv %s to %s\n",log,file1);
442		else
443			(void)rename(log, file1);
444	}
445
446        if (noaction)
447                printf("Start new log...");
448        else {
449                fd = creat(log,perm);
450                if (fd < 0)
451                        err(1, "can't start new log");
452                if (fchown(fd, owner_uid, group_gid))
453                        err(1, "can't chmod new log file");
454                (void) close(fd);
455                if (!(flags & CE_BINARY))
456                        if (log_trim(log))    /* Add status message */
457                             err(1, "can't add status message to log");
458        }
459        if (noaction)
460                printf("chmod %o %s...",perm,log);
461        else
462                (void) chmod(log,perm);
463
464	pid = 0;
465	need_notification = notified = 0;
466	if (pid_file != NULL) {
467		need_notification = 1;
468		pid = get_pid(pid_file);
469	} else if (needroot && !(flags & CE_BINARY)) {
470		need_notification = 1;
471		pid = syslog_pid;
472	}
473
474	if (pid) {
475		if (noaction) {
476			notified = 1;
477			printf("kill -HUP %d\n", (int)pid);
478		} else if (kill(pid,SIGHUP))
479			warn("can't notify daemon, pid %d", (int)pid);
480		else {
481			notified = 1;
482			if (verbose)
483				printf("daemon pid %d notified\n", (int)pid);
484		}
485	}
486
487	if ((flags & CE_COMPACT)) {
488		if (need_notification && !notified)
489			warnx("log not compressed because daemon not notified");
490		else if (noaction)
491                        printf("Compress %s.0\n",log);
492		else {
493			if (notified) {
494				if (verbose)
495					printf("small pause to allow daemon to close log\n");
496				sleep(3);
497			}
498                        compress_log(log);
499		}
500        }
501}
502
503/* Log the fact that the logs were turned over */
504static int log_trim(log)
505        char    *log;
506{
507        FILE    *f;
508        if ((f = fopen(log,"a")) == NULL)
509                return(-1);
510        fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
511                daytime, hostname, (int)getpid());
512        if (fclose(f) == EOF)
513                err(1, "log_trim: fclose:");
514        return(0);
515}
516
517/* Fork of /usr/ucb/compress to compress the old log file */
518static void compress_log(log)
519        char    *log;
520{
521	pid_t   pid;
522	char    tmp[MAXPATHLEN+1];
523
524        (void) sprintf(tmp,"%s.0",log);
525	pid = fork();
526        if (pid < 0)
527                err(1, "fork");
528        else if (!pid) {
529		(void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0);
530		err(1, COMPRESS_PATH);
531        }
532}
533
534/* Return size in kilobytes of a file */
535static int sizefile(file)
536        char    *file;
537{
538        struct stat sb;
539
540        if (stat(file,&sb) < 0)
541                return(-1);
542        return(kbytes(dbtob(sb.st_blocks)));
543}
544
545/* Return the age of old log file (file.0) */
546static int age_old_log(file)
547        char    *file;
548{
549        struct stat sb;
550        char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1];
551
552        (void) strcpy(tmp,file);
553        if (stat(strcat(tmp,".0"),&sb) < 0)
554            if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
555                return(-1);
556        return( (int) (timenow - sb.st_mtime + 1800) / 3600);
557}
558
559static pid_t get_pid(pid_file)
560	char *pid_file;
561{
562	FILE *f;
563	char  line[BUFSIZ];
564	pid_t pid = 0;
565
566	if ((f = fopen(pid_file,"r")) == NULL)
567		warn("can't open %s pid file to restart a daemon",
568			pid_file);
569	else {
570		if (fgets(line,BUFSIZ,f)) {
571			pid = atol(line);
572			if (pid < MIN_PID || pid > MAX_PID) {
573				warnx("preposterous process number: %d", (int)pid);
574				pid = 0;
575			}
576		} else
577			warn("can't read %s pid file to restart a daemon",
578				pid_file);
579		(void)fclose(f);
580	}
581	return pid;
582}
583
584#ifndef OSF
585/* Duplicate a string using malloc */
586
587char *strdup(strp)
588register char   *strp;
589{
590        register char *cp;
591
592        if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
593                abort();
594        return(strcpy (cp, strp));
595}
596#endif
597
598/* Skip Over Blanks */
599char *sob(p)
600	register char   *p;
601{
602        while (p && *p && isspace(*p))
603                p++;
604        return(p);
605}
606
607/* Skip Over Non-Blanks */
608char *son(p)
609	register char   *p;
610{
611        while (p && *p && !isspace(*p))
612                p++;
613        return(p);
614}
615