xref: /freebsd/usr.sbin/newsyslog/newsyslog.c (revision df7f5d4de4592a8948a25ce01e5bddfbb7ce39dc)
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: /home/ncvs/src/usr.sbin/newsyslog/newsyslog.c,v $
28  *      $Author: jkh $
29  */
30 
31 #ifndef lint
32 static char rcsid[] = "$Id$";
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 
78 struct 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 
90 char    *progname;              /* contains argv[0] */
91 int     verbose = 0;            /* Print out what's going on */
92 int     needroot = 1;           /* Root privs are necessary */
93 int     noaction = 0;           /* Don't do anything, just show it */
94 char    *conf = CONF;           /* Configuration file to use */
95 time_t  timenow;
96 int     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 */
99 char    hostname[MAXHOSTNAMELEN+1]; /* hostname */
100 char    *daytime;               /* timenow in human readable form */
101 
102 #ifndef OSF
103 char *strdup(char *strp);
104 #endif
105 
106 static struct conf_entry *parse_file();
107 static char *sob(char *p);
108 static char *son(char *p);
109 static char *missing_field(char *p,char *errline);
110 static void do_entry(struct conf_entry *ent);
111 static void PRS(int argc,char **argv);
112 static void usage();
113 static void dotrim(char *log,int numdays,int falgs,int perm, int owner_uid,int group_gid);
114 static int log_trim(char *log);
115 static void compress_log(char *log);
116 static int sizefile(char *file);
117 static int age_old_log(char *file);
118 
119 int 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 
140 static 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 
184 static 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 
234 static 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  */
244 static 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 
356 static 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 
364 static 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, _numdays;
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 	_numdays = numdays;	/* preserve */
400         while (numdays--) {
401                 (void) strcpy(file2,file1);
402                 (void) sprintf(file1,"%s.%d",log,numdays);
403                 (void) strcpy(zfile1, file1);
404                 (void) strcpy(zfile2, file2);
405                 if (lstat(file1, &st)) {
406                         (void) strcat(zfile1, COMPRESS_POSTFIX);
407                         (void) strcat(zfile2, COMPRESS_POSTFIX);
408                         if (lstat(zfile1, &st)) continue;
409                 }
410                 if (noaction) {
411                         printf("mv %s %s\n",zfile1,zfile2);
412                         printf("chmod %o %s\n", perm, zfile2);
413                         printf("chown %d.%d %s\n",
414                                owner_uid, group_gid, zfile2);
415                 } else {
416                         (void) rename(zfile1, zfile2);
417                         (void) chmod(zfile2, perm);
418                         (void) chown(zfile2, owner_uid, group_gid);
419                 }
420         }
421         if (!noaction && !(flags & CE_BINARY))
422                 (void) log_trim(log);  /* Report the trimming to the old log */
423 
424 	if (!_numdays) {
425 		if (noaction)
426 			printf("rm %s\n",log);
427 		else
428 			(void)unlink(log);
429 	}
430 	else {
431 		if (noaction)
432 			printf("mv %s to %s\n",log,file1);
433 		else
434 			(void)rename(log, file1);
435 	}
436 
437         if (noaction)
438                 printf("Start new log...");
439         else {
440                 fd = creat(log,perm);
441                 if (fd < 0)
442                         err(1, "can't start new log");
443                 if (fchown(fd, owner_uid, group_gid))
444                         err(1, "can't chmod new log file");
445                 (void) close(fd);
446                 if (!(flags & CE_BINARY))
447                         if (log_trim(log))    /* Add status message */
448                              err(1, "can't add status message to log");
449         }
450         if (noaction)
451                 printf("chmod %o %s...",perm,log);
452         else
453                 (void) chmod(log,perm);
454         if (noaction)
455                 printf("kill -HUP %d (syslogd)\n",syslog_pid);
456         else
457 	if (syslog_pid < MIN_PID || syslog_pid > MAX_PID) {
458 		warnx("preposterous process number: %d", syslog_pid);
459         } else if (kill(syslog_pid,SIGHUP))
460                 warn("could not restart syslogd");
461         if (flags & CE_COMPACT) {
462                 if (noaction)
463                         printf("Compress %s.0\n",log);
464                 else
465                         compress_log(log);
466         }
467 }
468 
469 /* Log the fact that the logs were turned over */
470 static int log_trim(log)
471         char    *log;
472 {
473         FILE    *f;
474         if ((f = fopen(log,"a")) == NULL)
475                 return(-1);
476         fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
477                 daytime, hostname, (int)getpid());
478         if (fclose(f) == EOF)
479                 err(1, "log_trim: fclose:");
480         return(0);
481 }
482 
483 /* Fork of /usr/ucb/compress to compress the old log file */
484 static void compress_log(log)
485         char    *log;
486 {
487         int     pid;
488         char    tmp[128];
489 
490         pid = fork();
491         (void) sprintf(tmp,"%s.0",log);
492         if (pid < 0)
493                 err(1, "fork");
494         else if (!pid) {
495                 (void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0);
496                 err(1, COMPRESS_PATH);
497         }
498 }
499 
500 /* Return size in kilobytes of a file */
501 static int sizefile(file)
502         char    *file;
503 {
504         struct stat sb;
505 
506         if (stat(file,&sb) < 0)
507                 return(-1);
508         return(kbytes(dbtob(sb.st_blocks)));
509 }
510 
511 /* Return the age of old log file (file.0) */
512 static int age_old_log(file)
513         char    *file;
514 {
515         struct stat sb;
516         char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1];
517 
518         (void) strcpy(tmp,file);
519         if (stat(strcat(tmp,".0"),&sb) < 0)
520             if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
521                 return(-1);
522         return( (int) (timenow - sb.st_mtime + 1800) / 3600);
523 }
524 
525 
526 #ifndef OSF
527 /* Duplicate a string using malloc */
528 
529 char *strdup(strp)
530 register char   *strp;
531 {
532         register char *cp;
533 
534         if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
535                 abort();
536         return(strcpy (cp, strp));
537 }
538 #endif
539 
540 /* Skip Over Blanks */
541 char *sob(p)
542         register char   *p;
543 {
544         while (p && *p && isspace(*p))
545                 p++;
546         return(p);
547 }
548 
549 /* Skip Over Non-Blanks */
550 char *son(p)
551         register char   *p;
552 {
553         while (p && *p && !isspace(*p))
554                 p++;
555         return(p);
556 }
557