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