xref: /netbsd-src/usr.bin/newsyslog/newsyslog.c (revision ae9172d6cd9432a6a1a56760d86b32c57a66c39c)
1 /*
2  * This file contains changes from the Open Software Foundation.
3  */
4 
5 /*
6 
7 Copyright 1988, 1989 by the Massachusetts Institute of Technology
8 
9 Permission to use, copy, modify, and distribute this software
10 and its documentation for any purpose and without fee is
11 hereby granted, provided that the above copyright notice
12 appear in all copies and that both that copyright notice and
13 this permission notice appear in supporting documentation,
14 and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
15 used in advertising or publicity pertaining to distribution
16 of the software without specific, written prior permission.
17 M.I.T. and the M.I.T. S.I.P.B. make no representations about
18 the suitability of this software for any purpose.  It is
19 provided "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: /cvsroot/src/usr.bin/newsyslog/newsyslog.c,v $
28  *      $Author: pk $
29  */
30 
31 #ifndef lint
32 static char rcsid[] = "$Id: newsyslog.c,v 1.7 1994/02/20 09:54:45 pk 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
42 #define COMPRESS "/usr/ucb/compress" /* File compression program */
43 #endif
44 #ifndef COMPRESS_POSTFIX
45 #define COMPRESS_POSTFIX ".Z"
46 #endif
47 
48 #include <stdio.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 
178         progname = argv[0];
179         timenow = time((time_t *) 0);
180         daytime = ctime(&timenow);
181         daytime[strlen(daytime)-1] = '\0';
182 
183         /* Let's find the pid of syslogd */
184         syslog_pid = 0;
185         f = fopen(PIDFILE,"r");
186         if (f && fgets(line,BUFSIZ,f))
187                 syslog_pid = atoi(line);
188 	if (f)
189 		(void)fclose(f);
190 
191         /* Let's get our hostname */
192         if (gethostname(hostname, 64)) {
193                 perror("gethostname");
194                 (void) strcpy(hostname,"Mystery Host");
195         }
196 
197         optind = 1;             /* Start options parsing */
198         while ((c=getopt(argc,argv,"nrvf:t:")) != EOF)
199                 switch (c) {
200                 case 'n':
201                         noaction++; /* This implies needroot as off */
202                         /* fall through */
203                 case 'r':
204                         needroot = 0;
205                         break;
206                 case 'v':
207                         verbose++;
208                         break;
209                 case 'f':
210                         conf = optarg;
211                         break;
212                 default:
213                         usage();
214                 }
215         }
216 
217 usage()
218 {
219         fprintf(stderr,
220                 "Usage: %s <-nrv> <-f config-file>\n");
221         exit(1);
222 }
223 
224 /* Parse a configuration file and return a linked list of all the logs
225  * to process
226  */
227 struct conf_entry *parse_file()
228 {
229         FILE    *f;
230         char    line[BUFSIZ], *parse, *q;
231         char    *errline, *group;
232         struct conf_entry *first = NULL;
233         struct conf_entry *working;
234         struct passwd *pass;
235         struct group *grp;
236 
237         if (strcmp(conf,"-"))
238                 f = fopen(conf,"r");
239         else
240                 f = stdin;
241         if (!f) {
242                 (void) fprintf(stderr,"%s: ",progname);
243                 perror(conf);
244                 exit(1);
245         }
246         while (fgets(line,BUFSIZ,f)) {
247                 if ((line[0]== '\n') || (line[0] == '#'))
248                         continue;
249                 errline = strdup(line);
250                 if (!first) {
251                         working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
252                         first = working;
253                 } else {
254                         working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
255                         working = working->next;
256                 }
257 
258                 q = parse = missing_field(sob(line),errline);
259                 *(parse = son(line)) = '\0';
260                 working->log = strdup(q);
261 
262                 q = parse = missing_field(sob(++parse),errline);
263                 *(parse = son(parse)) = '\0';
264                 if ((group = strchr(q, '.')) != NULL) {
265                     *group++ = '\0';
266                     if (*q) {
267                         if (!(isnumber(q))) {
268                             if ((pass = getpwnam(q)) == NULL) {
269                                 fprintf(stderr,
270                                     "Error in config file; unknown user:\n");
271                                 fputs(errline,stderr);
272                                 exit(1);
273                             }
274                             working->uid = pass->pw_uid;
275                         } else
276                             working->uid = atoi(q);
277                     } else
278                         working->uid = NONE;
279 
280                     q = group;
281                     if (*q) {
282                         if (!(isnumber(q))) {
283                             if ((grp = getgrnam(q)) == NULL) {
284                                 fprintf(stderr,
285                                     "Error in config file; unknown group:\n");
286                                 fputs(errline,stderr);
287                                 exit(1);
288                             }
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                         fprintf(stderr,
303                                 "Error in config file; bad permissions:\n");
304                         fputs(errline,stderr);
305                         exit(1);
306                 }
307 
308                 q = parse = missing_field(sob(++parse),errline);
309                 *(parse = son(parse)) = '\0';
310                 if (!sscanf(q,"%d",&working->numlogs)) {
311                         fprintf(stderr,
312                                 "Error in config file; bad number:\n");
313                         fputs(errline,stderr);
314                         exit(1);
315                 }
316 
317                 q = parse = missing_field(sob(++parse),errline);
318                 *(parse = son(parse)) = '\0';
319                 if (isdigit(*q))
320                         working->size = atoi(q);
321                 else
322                         working->size = -1;
323 
324                 q = parse = missing_field(sob(++parse),errline);
325                 *(parse = son(parse)) = '\0';
326                 if (isdigit(*q))
327                         working->hours = atoi(q);
328                 else
329                         working->hours = -1;
330 
331                 q = parse = sob(++parse); /* Optional field */
332                 *(parse = son(parse)) = '\0';
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                                 fprintf(stderr,
341                                         "Illegal flag in config file -- %c\n",
342                                         *q);
343                                 exit(1);
344                         }
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 
356 char *missing_field(p,errline)
357         char    *p,*errline;
358 {
359         if (!p || !*p) {
360                 fprintf(stderr,"Missing field in config file:\n");
361                 fputs(errline,stderr);
362                 exit(1);
363         }
364         return(p);
365 }
366 
367 dotrim(log,numdays,flags,perm,owner_uid,group_gid)
368         char    *log;
369         int     numdays;
370         int     flags;
371         int     perm;
372         int     owner_uid;
373         int     group_gid;
374 {
375         char    file1[128], file2[128];
376         char    zfile1[128], zfile2[128];
377         int     fd;
378         struct  stat st;
379 
380 #ifdef _IBMR2
381 /* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
382 /* change it to be owned by uid -1, instead of leaving it as is, as it is */
383 /* supposed to. */
384                 if (owner_uid == -1)
385                   owner_uid = geteuid();
386 #endif
387 
388         /* Remove oldest log */
389         (void) sprintf(file1,"%s.%d",log,numdays);
390         (void) strcpy(zfile1, file1);
391         (void) strcat(zfile1, COMPRESS_POSTFIX);
392 
393         if (noaction) {
394                 printf("rm -f %s\n", file1);
395                 printf("rm -f %s\n", zfile1);
396         } else {
397                 (void) unlink(file1);
398                 (void) unlink(zfile1);
399         }
400 
401         /* Move down log files */
402         while (numdays--) {
403                 (void) strcpy(file2,file1);
404                 (void) sprintf(file1,"%s.%d",log,numdays);
405                 (void) strcpy(zfile1, file1);
406                 (void) strcpy(zfile2, file2);
407                 if (lstat(file1, &st)) {
408                         (void) strcat(zfile1, COMPRESS_POSTFIX);
409                         (void) strcat(zfile2, COMPRESS_POSTFIX);
410                         if (lstat(zfile1, &st)) continue;
411                 }
412                 if (noaction) {
413                         printf("mv %s %s\n",zfile1,zfile2);
414                         printf("chmod %o %s\n", perm, zfile2);
415                         printf("chown %d.%d %s\n",
416                                owner_uid, group_gid, zfile2);
417                 } else {
418                         (void) rename(zfile1, zfile2);
419                         (void) chmod(zfile2, perm);
420                         (void) chown(zfile2, owner_uid, group_gid);
421                 }
422         }
423         if (!noaction && !(flags & CE_BINARY))
424                 (void) log_trim(log);  /* Report the trimming to the old log */
425 
426         if (noaction)
427                 printf("mv %s to %s\n",log,file1);
428         else
429                 (void) rename(log,file1);
430         if (noaction)
431                 printf("Start new log...");
432         else {
433                 fd = creat(log,perm);
434                 if (fd < 0) {
435                         perror("can't start new log");
436                         exit(1);
437                 }
438                 if (fchown(fd, owner_uid, group_gid)) {
439                         perror("can't chmod new log file");
440                         exit(1);
441                 }
442                 (void) close(fd);
443                 if (!(flags & CE_BINARY))
444                         if (log_trim(log)) {    /* Add status message */
445                                 perror("can't add status message to log");
446                                 exit(1);
447                         }
448         }
449         if (noaction)
450                 printf("chmod %o %s...",perm,log);
451         else
452                 (void) chmod(log,perm);
453         if (noaction)
454                 printf("kill -HUP %d (syslogd)\n",syslog_pid);
455         else
456 	if (syslog_pid < MIN_PID || syslog_pid > MAX_PID) {
457 		fprintf(stderr,"%s: preposterous process number: %d\n",
458 				progname, syslog_pid);
459         } else if (kill(syslog_pid,SIGHUP)) {
460                         fprintf(stderr,"%s: ",progname);
461                         perror("warning - could not restart syslogd");
462                 }
463         if (flags & CE_COMPACT) {
464                 if (noaction)
465                         printf("Compress %s.0\n",log);
466                 else
467                         compress_log(log);
468         }
469 }
470 
471 /* Log the fact that the logs were turned over */
472 log_trim(log)
473         char    *log;
474 {
475         FILE    *f;
476         if ((f = fopen(log,"a")) == NULL)
477                 return(-1);
478         fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
479                 daytime, hostname, getpid());
480         if (fclose(f) == EOF) {
481                 perror("log_trim: fclose:");
482                 exit(1);
483         }
484         return(0);
485 }
486 
487 /* Fork of /usr/ucb/compress to compress the old log file */
488 compress_log(log)
489         char    *log;
490 {
491         int     pid;
492         char    tmp[128];
493 
494         pid = fork();
495         (void) sprintf(tmp,"%s.0",log);
496         if (pid < 0) {
497                 fprintf(stderr,"%s: ",progname);
498                 perror("fork");
499                 exit(1);
500         } else if (!pid) {
501                 (void) execl(COMPRESS,"compress","-f",tmp,0);
502                 fprintf(stderr,"%s: ",progname);
503                 perror(COMPRESS);
504                 exit(1);
505         }
506 }
507 
508 /* Return size in kilobytes of a file */
509 int sizefile(file)
510         char    *file;
511 {
512         struct stat sb;
513 
514         if (stat(file,&sb) < 0)
515                 return(-1);
516         return(kbytes(dbtob(sb.st_blocks)));
517 }
518 
519 /* Return the age of old log file (file.0) */
520 int age_old_log(file)
521         char    *file;
522 {
523         struct stat sb;
524         char tmp[MAXPATHLEN+3];
525 
526         (void) strcpy(tmp,file);
527         if (stat(strcat(tmp,".0"),&sb) < 0)
528             if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
529                 return(-1);
530         return( (int) (timenow - sb.st_mtime + 1800) / 3600);
531 }
532 
533 
534 #ifndef OSF
535 /* Duplicate a string using malloc */
536 
537 char *strdup(strp)
538 register char   *strp;
539 {
540         register char *cp;
541 
542         if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
543                 abort();
544         return(strcpy (cp, strp));
545 }
546 #endif
547 
548 /* Skip Over Blanks */
549 char *sob(p)
550         register char   *p;
551 {
552         while (p && *p && isspace(*p))
553                 p++;
554         return(p);
555 }
556 
557 /* Skip Over Non-Blanks */
558 char *son(p)
559         register char   *p;
560 {
561         while (p && *p && !isspace(*p))
562                 p++;
563         return(p);
564 }
565 
566 
567 /* Check if string is actually a number */
568 
569 isnumber(string)
570 char *string;
571 {
572         while (*string != '\0') {
573             if (*string < '0' || *string > '9') return(0);
574             string++;
575         }
576         return(1);
577 }
578