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