newsyslog.c revision 36817
160786Sps/*
2161475Sdelphij * This file contains changes from the Open Software Foundation.
360786Sps */
460786Sps
560786Sps/*
660786Sps
760786SpsCopyright 1988, 1989 by the Massachusetts Institute of Technology
860786Sps
960786SpsPermission to use, copy, modify, and distribute this software
1060786Spsand its documentation for any purpose and without fee is
1160786Spshereby granted, provided that the above copyright notice
1260786Spsappear in all copies and that both that copyright notice and
1360786Spsthis permission notice appear in supporting documentation,
1460786Spsand that the names of M.I.T. and the M.I.T. S.I.P.B. not be
1560786Spsused in advertising or publicity pertaining to distribution
1660786Spsof the software without specific, written prior permission.
1760786SpsM.I.T. and the M.I.T. S.I.P.B. make no representations about
1860786Spsthe suitability of this software for any purpose.  It is
1960786Spsprovided "as is" without express or implied warranty.
2060786Sps
2160786Sps*/
2260786Sps
2360786Sps/*
2460786Sps *      newsyslog - roll over selected logs at the appropriate time,
2560786Sps *              keeping the a specified number of backup files around.
2660786Sps */
2760786Sps
2860786Sps#ifndef lint
2960786Spsstatic const char rcsid[] =
3060786Sps	"$Id: newsyslog.c,v 1.19 1998/05/10 21:13:29 hoek Exp $";
3160786Sps#endif /* not lint */
3260786Sps
3360786Sps#ifndef CONF
3460786Sps#define CONF "/etc/athena/newsyslog.conf" /* Configuration file */
3560786Sps#endif
3660786Sps#ifndef PIDFILE
3760786Sps#define PIDFILE "/etc/syslog.pid"
3860786Sps#endif
3960786Sps#ifndef COMPRESS_PATH
4060786Sps#define COMPRESS_PATH "/usr/ucb/compress" /* File compression program */
4160786Sps#endif
4260786Sps#ifndef COMPRESS_PROG
4360786Sps#define COMPRESS_PROG "compress"
4460786Sps#endif
4560786Sps#ifndef COMPRESS_POSTFIX
4660786Sps#define COMPRESS_POSTFIX ".Z"
4760786Sps#endif
4860786Sps
4960786Sps#include <ctype.h>
5060786Sps#include <err.h>
5160786Sps#include <fcntl.h>
5260786Sps#include <grp.h>
5360786Sps#include <pwd.h>
5460786Sps#include <signal.h>
5560786Sps#include <stdio.h>
5660786Sps#include <stdlib.h>
5760786Sps#include <string.h>
5860786Sps#include <unistd.h>
5960786Sps#include <sys/types.h>
6060786Sps#include <sys/time.h>
6160786Sps#include <sys/stat.h>
6260786Sps#include <sys/param.h>
6360786Sps#include <sys/wait.h>
6460786Sps
6560786Sps#define kbytes(size)  (((size) + 1023) >> 10)
6660786Sps#ifdef _IBMR2
6760786Sps/* Calculates (db * DEV_BSIZE) */
6860786Sps#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
6960786Sps#endif
7060786Sps
7160786Sps#define CE_COMPACT 1            /* Compact the achived log files */
7260786Sps#define CE_BINARY  2            /* Logfile is in binary, don't add */
7360786Sps                                /* status messages */
7460786Sps#define NONE -1
7560786Sps
7660786Spsstruct conf_entry {
7760786Sps        char    *log;           /* Name of the log */
7860786Sps	char	*pid_file;	/* PID file */
7960786Sps        int     uid;            /* Owner of log */
8060786Sps        int     gid;            /* Group of log */
8160786Sps        int     numlogs;        /* Number of logs to keep */
8260786Sps        int     size;           /* Size cutoff to trigger trimming the log */
8360786Sps        int     hours;          /* Hours between log trimming */
8460786Sps        int     permissions;    /* File permissions on the log */
8560786Sps        int     flags;          /* Flags (CE_COMPACT & CE_BINARY)  */
8660786Sps	int     sig;            /* Signal to send */
8760786Sps        struct conf_entry       *next; /* Linked list pointer */
8860786Sps};
8960786Sps
9060786Spsint     verbose = 0;            /* Print out what's going on */
9160786Spsint     needroot = 1;           /* Root privs are necessary */
9260786Spsint     noaction = 0;           /* Don't do anything, just show it */
9360786Spsint     force = 0;		/* Force the trim no matter what*/
9460786Spschar    *conf = CONF;           /* Configuration file to use */
9560786Spstime_t  timenow;
9660786Sps#define MIN_PID         5
9760786Sps#define MAX_PID		30000   /* was 65534, see /usr/include/sys/proc.h */
9860786Spschar    hostname[MAXHOSTNAMELEN+1]; /* hostname */
9963128Spschar    *daytime;               /* timenow in human readable form */
10063128Sps
10163128Sps#ifndef OSF
10263128Spschar *strdup(char *strp);
10363128Sps#endif
10463128Sps
10563128Spsstatic struct conf_entry *parse_file();
10660786Spsstatic char *sob(char *p);
10760786Spsstatic char *son(char *p);
10860786Spsstatic char *missing_field(char *p,char *errline);
10960786Spsstatic void do_entry(struct conf_entry *ent);
11060786Spsstatic void PRS(int argc,char **argv);
11160786Spsstatic void usage();
11260786Spsstatic void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm,int owner_uid,int group_gid,int sig);
11363128Spsstatic int log_trim(char *log);
11460786Spsstatic void compress_log(char *log);
11560786Spsstatic int sizefile(char *file);
11660786Spsstatic int age_old_log(char *file);
11760786Spsstatic pid_t get_pid(char *pid_file);
11860786Sps
11960786Spsint main(argc,argv)
12060786Sps        int argc;
12160786Sps        char **argv;
12260786Sps{
12360786Sps        struct conf_entry *p, *q;
12460786Sps
12560786Sps        PRS(argc,argv);
12660786Sps        if (needroot && getuid() && geteuid())
12760786Sps                errx(1, "must have root privs");
12860786Sps        p = q = parse_file();
12960786Sps
13060786Sps        while (p) {
13160786Sps                do_entry(p);
13260786Sps                p=p->next;
13360786Sps                free((char *) q);
13460786Sps                q=p;
13560786Sps        }
13660786Sps        return(0);
13760786Sps}
13860786Sps
13960786Spsstatic void do_entry(ent)
14060786Sps        struct conf_entry       *ent;
14160786Sps
14260786Sps{
14360786Sps        int     size, modtime;
14460786Sps	char 	*pid_file;
14560786Sps
14660786Sps        if (verbose) {
14760786Sps                if (ent->flags & CE_COMPACT)
14860786Sps                        printf("%s <%dZ>: ",ent->log,ent->numlogs);
14960786Sps                else
15060786Sps                        printf("%s <%d>: ",ent->log,ent->numlogs);
15160786Sps        }
15260786Sps        size = sizefile(ent->log);
15360786Sps        modtime = age_old_log(ent->log);
15460786Sps        if (size < 0) {
15560786Sps                if (verbose)
15660786Sps                        printf("does not exist.\n");
15760786Sps        } else {
15860786Sps                if (verbose && (ent->size > 0))
15960786Sps                        printf("size (Kb): %d [%d] ", size, ent->size);
16060786Sps                if (verbose && (ent->hours > 0))
16160786Sps                        printf(" age (hr): %d [%d] ", modtime, ent->hours);
16260786Sps                if (force || ((ent->size > 0) && (size >= ent->size)) ||
16360786Sps                    ((ent->hours > 0) && ((modtime >= ent->hours)
16460786Sps                                        || (modtime < 0)))) {
16560786Sps                        if (verbose)
16660786Sps                                printf("--> trimming log....\n");
16760786Sps                        if (noaction && !verbose) {
16860786Sps                                if (ent->flags & CE_COMPACT)
16960786Sps                                        printf("%s <%dZ>: trimming\n",
17060786Sps                                               ent->log,ent->numlogs);
17160786Sps                                else
17260786Sps                                        printf("%s <%d>: trimming\n",
17360786Sps                                               ent->log,ent->numlogs);
17460786Sps                        }
17560786Sps			if (ent->pid_file) {
17660786Sps				pid_file = ent->pid_file;
17760786Sps			} else {
17860786Sps				/* Only try to notify syslog if we are root */
17960786Sps				if (needroot)
18060786Sps					pid_file = PIDFILE;
18160786Sps				else
18260786Sps					pid_file = NULL;
18360786Sps			}
18460786Sps                        dotrim(ent->log, pid_file, ent->numlogs,
18560786Sps			    ent->flags, ent->permissions, ent->uid, ent->gid, ent->sig);
18660786Sps                } else {
18760786Sps                        if (verbose)
18860786Sps                                printf("--> skipping\n");
18960786Sps                }
19060786Sps        }
19160786Sps}
19260786Sps
19360786Spsstatic void PRS(argc,argv)
19460786Sps        int argc;
19560786Sps        char **argv;
19660786Sps{
19760786Sps        int     c;
19860786Sps	char	*p;
19960786Sps
20060786Sps        timenow = time((time_t *) 0);
20160786Sps        daytime = ctime(&timenow) + 4;
20260786Sps        daytime[15] = '\0';
20360786Sps
20460786Sps        /* Let's get our hostname */
20560786Sps        (void) gethostname(hostname, sizeof(hostname));
20660786Sps
20760786Sps	/* Truncate domain */
20860786Sps	if ((p = strchr(hostname, '.'))) {
20960786Sps		*p = '\0';
21060786Sps	}
21160786Sps
21260786Sps        optind = 1;             /* Start options parsing */
21360786Sps        while ((c=getopt(argc,argv,"nrvFf:t:")) != -1)
21460786Sps                switch (c) {
21560786Sps                case 'n':
21660786Sps                        noaction++;
21760786Sps			break;
21860786Sps                case 'r':
219161475Sdelphij                        needroot = 0;
22060786Sps                        break;
22160786Sps                case 'v':
22260786Sps                        verbose++;
22360786Sps                        break;
22460786Sps                case 'f':
22560786Sps                        conf = optarg;
22660786Sps                        break;
22760786Sps		case 'F':
228161475Sdelphij			force++;
229161475Sdelphij			break;
230161475Sdelphij                default:
23160786Sps                        usage();
23260786Sps                }
23360786Sps        }
23460786Sps
23560786Spsstatic void usage()
23660786Sps{
23760786Sps        fprintf(stderr, "usage: newsyslog [-Fnrv] [-f config-file]\n");
23860786Sps        exit(1);
23960786Sps}
24060786Sps
24160786Sps/* Parse a configuration file and return a linked list of all the logs
24289019Sps * to process
24389019Sps */
24489019Spsstatic struct conf_entry *parse_file()
24589019Sps{
24660786Sps        FILE    *f;
24760786Sps        char    line[BUFSIZ], *parse, *q;
24860786Sps        char    *errline, *group;
24960786Sps        struct conf_entry *first = NULL;
25060786Sps        struct conf_entry *working = NULL;
25160786Sps        struct passwd *pass;
25260786Sps        struct group *grp;
25360786Sps	int eol;
25460786Sps
25560786Sps        if (strcmp(conf,"-"))
25660786Sps                f = fopen(conf,"r");
25760786Sps        else
25889019Sps                f = stdin;
25989019Sps        if (!f)
26089019Sps                err(1, "%s", conf);
26189019Sps        while (fgets(line,BUFSIZ,f)) {
26289019Sps                if ((line[0]== '\n') || (line[0] == '#'))
26389019Sps                        continue;
26460786Sps                errline = strdup(line);
26560786Sps                if (!first) {
26660786Sps                        working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
26760786Sps                        first = working;
26889019Sps                } else {
26989019Sps                        working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
27089019Sps                        working = working->next;
27189019Sps                }
27289019Sps
27389019Sps                q = parse = missing_field(sob(line),errline);
27489019Sps                parse = son(line);
27589019Sps		if (!*parse)
27689019Sps                  errx(1, "malformed line (missing fields):\n%s", errline);
27789019Sps                *parse = '\0';
27889019Sps                working->log = strdup(q);
27989019Sps
28089019Sps                q = parse = missing_field(sob(++parse),errline);
28189019Sps                parse = son(parse);
28289019Sps		if (!*parse)
28389019Sps                  errx(1, "malformed line (missing fields):\n%s", errline);
28489019Sps                *parse = '\0';
28589019Sps                if ((group = strchr(q, '.')) != NULL) {
28689019Sps                    *group++ = '\0';
28789019Sps                    if (*q) {
28889019Sps                        if (!(isnumber(*q))) {
28989019Sps                            if ((pass = getpwnam(q)) == NULL)
29089019Sps                                errx(1,
29189019Sps                                  "error in config file; unknown user:\n%s",
29289019Sps                                  errline);
29389019Sps                            working->uid = pass->pw_uid;
29489019Sps                        } else
29589019Sps                            working->uid = atoi(q);
29689019Sps                    } else
29789019Sps                        working->uid = NONE;
29889019Sps
29989019Sps                    q = group;
30089019Sps                    if (*q) {
30189019Sps                        if (!(isnumber(*q))) {
30289019Sps                            if ((grp = getgrnam(q)) == NULL)
30360786Sps                                errx(1,
30460786Sps                                  "error in config file; unknown group:\n%s",
30560786Sps                                  errline);
30660786Sps                            working->gid = grp->gr_gid;
30760786Sps                        } else
30860786Sps                            working->gid = atoi(q);
30960786Sps                    } else
31060786Sps                        working->gid = NONE;
31160786Sps
31260786Sps                    q = parse = missing_field(sob(++parse),errline);
31360786Sps                    parse = son(parse);
31460786Sps		    if (!*parse)
31560786Sps                      errx(1, "malformed line (missing fields):\n%s", errline);
31660786Sps                    *parse = '\0';
31760786Sps                }
31889019Sps                else
31960786Sps                    working->uid = working->gid = NONE;
32060786Sps
32160786Sps                if (!sscanf(q,"%o",&working->permissions))
32260786Sps                        errx(1, "error in config file; bad permissions:\n%s",
32360786Sps                          errline);
32460786Sps
32560786Sps                q = parse = missing_field(sob(++parse),errline);
32660786Sps                parse = son(parse);
32760786Sps		if (!*parse)
32860786Sps                  errx(1, "malformed line (missing fields):\n%s", errline);
32960786Sps                *parse = '\0';
330                if (!sscanf(q,"%d",&working->numlogs))
331                        errx(1, "error in config file; bad number:\n%s",
332                          errline);
333
334                q = parse = missing_field(sob(++parse),errline);
335                parse = son(parse);
336		if (!*parse)
337                  errx(1, "malformed line (missing fields):\n%s", errline);
338                *parse = '\0';
339                if (isdigit(*q))
340                        working->size = atoi(q);
341                else
342                        working->size = -1;
343
344                q = parse = missing_field(sob(++parse),errline);
345                parse = son(parse);
346		eol = !*parse;
347                *parse = '\0';
348                if (isdigit(*q))
349                        working->hours = atoi(q);
350                else
351                        working->hours = -1;
352
353		if (eol)
354		  q = NULL;
355		else {
356                  q = parse = sob(++parse); /* Optional field */
357                  parse = son(parse);
358		  if (!*parse)
359                    eol = 1;
360                  *parse = '\0';
361		}
362
363                working->flags = 0;
364                while (q && *q && !isspace(*q)) {
365                        if ((*q == 'Z') || (*q == 'z'))
366                                working->flags |= CE_COMPACT;
367                        else if ((*q == 'B') || (*q == 'b'))
368                                working->flags |= CE_BINARY;
369                        else if (*q != '-')
370                           errx(1, "illegal flag in config file -- %c", *q);
371                        q++;
372                }
373
374		if (eol)
375		  q = NULL;
376		else {
377		  q = parse = sob(++parse); /* Optional field */
378                  parse = son(parse);
379		  if (!*parse)
380                    eol = 1;
381                  *parse = '\0';
382		}
383
384		working->pid_file = NULL;
385		if (q && *q) {
386			if (*q == '/')
387				working->pid_file = strdup(q);
388			else if (isdigit(*q))
389				goto got_sig;
390                        else
391			   errx(1, "illegal pid file or signal number in config file:\n%s", errline);
392		}
393
394		if (eol)
395		  q = NULL;
396		else {
397		  q = parse = sob(++parse); /* Optional field */
398		  *(parse = son(parse)) = '\0';
399		}
400
401		working->sig = SIGHUP;
402		if (q && *q) {
403			if (isdigit(*q)) {
404			got_sig:
405				working->sig = atoi(q);
406			} else {
407			err_sig:
408			   errx(1, "illegal signal number in config file:\n%s", errline);
409			}
410			if (working->sig < 1 || working->sig >= NSIG)
411				goto err_sig;
412		}
413
414                free(errline);
415        }
416        if (working)
417                working->next = (struct conf_entry *) NULL;
418        (void) fclose(f);
419        return(first);
420}
421
422static char *missing_field(p,errline)
423        char    *p,*errline;
424{
425        if (!p || !*p)
426            errx(1, "missing field in config file:\n%s", errline);
427        return(p);
428}
429
430static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid,sig)
431        char    *log;
432	char    *pid_file;
433        int     numdays;
434        int     flags;
435        int     perm;
436        int     owner_uid;
437        int     group_gid;
438	int     sig;
439{
440        char    file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1];
441        char    zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1];
442	int     notified, need_notification, fd, _numdays;
443        struct  stat st;
444	pid_t   pid;
445
446#ifdef _IBMR2
447/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
448/* change it to be owned by uid -1, instead of leaving it as is, as it is */
449/* supposed to. */
450                if (owner_uid == -1)
451                  owner_uid = geteuid();
452#endif
453
454        /* Remove oldest log */
455        (void) sprintf(file1,"%s.%d",log,numdays);
456        (void) strcpy(zfile1, file1);
457        (void) strcat(zfile1, COMPRESS_POSTFIX);
458
459        if (noaction) {
460                printf("rm -f %s\n", file1);
461                printf("rm -f %s\n", zfile1);
462        } else {
463                (void) unlink(file1);
464                (void) unlink(zfile1);
465        }
466
467        /* Move down log files */
468	_numdays = numdays;	/* preserve */
469        while (numdays--) {
470                (void) strcpy(file2,file1);
471                (void) sprintf(file1,"%s.%d",log,numdays);
472                (void) strcpy(zfile1, file1);
473                (void) strcpy(zfile2, file2);
474                if (lstat(file1, &st)) {
475                        (void) strcat(zfile1, COMPRESS_POSTFIX);
476                        (void) strcat(zfile2, COMPRESS_POSTFIX);
477			if (lstat(zfile1, &st)) continue;
478                }
479                if (noaction) {
480                        printf("mv %s %s\n",zfile1,zfile2);
481                        printf("chmod %o %s\n", perm, zfile2);
482                        printf("chown %d.%d %s\n",
483                               owner_uid, group_gid, zfile2);
484                } else {
485                        (void) rename(zfile1, zfile2);
486                        (void) chmod(zfile2, perm);
487                        (void) chown(zfile2, owner_uid, group_gid);
488                }
489        }
490        if (!noaction && !(flags & CE_BINARY))
491                (void) log_trim(log);  /* Report the trimming to the old log */
492
493	if (!_numdays) {
494		if (noaction)
495			printf("rm %s\n",log);
496		else
497			(void)unlink(log);
498	}
499	else {
500		if (noaction)
501			printf("mv %s to %s\n",log,file1);
502		else
503			(void)rename(log, file1);
504	}
505
506        if (noaction)
507                printf("Start new log...");
508        else {
509                fd = creat(log,perm);
510                if (fd < 0)
511                        err(1, "can't start new log");
512                if (fchown(fd, owner_uid, group_gid))
513                        err(1, "can't chmod new log file");
514                (void) close(fd);
515                if (!(flags & CE_BINARY))
516                        if (log_trim(log))    /* Add status message */
517                             err(1, "can't add status message to log");
518        }
519        if (noaction)
520                printf("chmod %o %s...",perm,log);
521        else
522                (void) chmod(log,perm);
523
524	pid = 0;
525	need_notification = notified = 0;
526	if (pid_file != NULL) {
527		need_notification = 1;
528		pid = get_pid(pid_file);
529	}
530
531	if (pid) {
532		if (noaction) {
533			notified = 1;
534			printf("kill -%d %d\n", sig, (int)pid);
535		} else if (kill(pid,sig))
536			warn("can't notify daemon, pid %d", (int)pid);
537		else {
538			notified = 1;
539			if (verbose)
540				printf("daemon pid %d notified\n", (int)pid);
541		}
542	}
543
544	if ((flags & CE_COMPACT)) {
545		if (need_notification && !notified)
546			warnx("log not compressed because daemon not notified");
547		else if (noaction)
548                        printf("Compress %s.0\n",log);
549		else {
550			if (notified) {
551				if (verbose)
552					printf("small pause to allow daemon to close log\n");
553				sleep(10);
554			}
555                        compress_log(log);
556		}
557        }
558}
559
560/* Log the fact that the logs were turned over */
561static int log_trim(log)
562        char    *log;
563{
564        FILE    *f;
565        if ((f = fopen(log,"a")) == NULL)
566                return(-1);
567        fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
568                daytime, hostname, (int)getpid());
569        if (fclose(f) == EOF)
570                err(1, "log_trim: fclose:");
571        return(0);
572}
573
574/* Fork of /usr/ucb/compress to compress the old log file */
575static void compress_log(log)
576        char    *log;
577{
578	pid_t   pid;
579	char    tmp[MAXPATHLEN+1];
580
581        (void) sprintf(tmp,"%s.0",log);
582	pid = fork();
583        if (pid < 0)
584                err(1, "fork");
585        else if (!pid) {
586		(void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0);
587		err(1, COMPRESS_PATH);
588        }
589}
590
591/* Return size in kilobytes of a file */
592static int sizefile(file)
593        char    *file;
594{
595        struct stat sb;
596
597        if (stat(file,&sb) < 0)
598                return(-1);
599        return(kbytes(dbtob(sb.st_blocks)));
600}
601
602/* Return the age of old log file (file.0) */
603static int age_old_log(file)
604        char    *file;
605{
606        struct stat sb;
607        char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1];
608
609        (void) strcpy(tmp,file);
610        if (stat(strcat(tmp,".0"),&sb) < 0)
611            if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
612                return(-1);
613        return( (int) (timenow - sb.st_mtime + 1800) / 3600);
614}
615
616static pid_t get_pid(pid_file)
617	char *pid_file;
618{
619	FILE *f;
620	char  line[BUFSIZ];
621	pid_t pid = 0;
622
623	if ((f = fopen(pid_file,"r")) == NULL)
624		warn("can't open %s pid file to restart a daemon",
625			pid_file);
626	else {
627		if (fgets(line,BUFSIZ,f)) {
628			pid = atol(line);
629			if (pid < MIN_PID || pid > MAX_PID) {
630				warnx("preposterous process number: %d", (int)pid);
631				pid = 0;
632			}
633		} else
634			warn("can't read %s pid file to restart a daemon",
635				pid_file);
636		(void)fclose(f);
637	}
638	return pid;
639}
640
641#ifndef OSF
642/* Duplicate a string using malloc */
643
644char *strdup(strp)
645register char   *strp;
646{
647        register char *cp;
648
649        if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
650                abort();
651        return(strcpy (cp, strp));
652}
653#endif
654
655/* Skip Over Blanks */
656char *sob(p)
657	register char   *p;
658{
659        while (p && *p && isspace(*p))
660                p++;
661        return(p);
662}
663
664/* Skip Over Non-Blanks */
665char *son(p)
666	register char   *p;
667{
668        while (p && *p && !isspace(*p))
669                p++;
670        return(p);
671}
672