xref: /openbsd-src/usr.bin/newsyslog/newsyslog.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: newsyslog.c,v 1.37 2001/07/09 07:04:50 deraadt Exp $	*/
2 
3 /*
4  * Copyright (c) 1999 Todd C. Miller <Todd.Miller@courtesan.com>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. The name of the author may not be used to endorse or promote products
16  *    derived from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
19  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
20  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
21  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
24  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
27  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 /*
31  * Copyright (c) 1997, Jason Downs.  All rights reserved.
32  *
33  * Redistribution and use in source and binary forms, with or without
34  * modification, are permitted provided that the following conditions
35  * are met:
36  * 1. Redistributions of source code must retain the above copyright
37  *    notice, this list of conditions and the following disclaimer.
38  * 2. Redistributions in binary form must reproduce the above copyright
39  *    notice, this list of conditions and the following disclaimer in the
40  *    documentation and/or other materials provided with the distribution.
41  * 3. All advertising materials mentioning features or use of this software
42  *    must display the following acknowledgement:
43  *      This product includes software developed by Jason Downs for the
44  *      OpenBSD system.
45  * 4. Neither the name(s) of the author(s) nor the name OpenBSD
46  *    may be used to endorse or promote products derived from this software
47  *    without specific prior written permission.
48  *
49  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS
50  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
51  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
52  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
53  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
54  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
55  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
56  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
57  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
58  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
59  * SUCH DAMAGE.
60  */
61 
62 /*
63  * This file contains changes from the Open Software Foundation.
64  */
65 
66 /*
67 
68 Copyright 1988, 1989 by the Massachusetts Institute of Technology
69 
70 Permission to use, copy, modify, and distribute this software
71 and its documentation for any purpose and without fee is
72 hereby granted, provided that the above copyright notice
73 appear in all copies and that both that copyright notice and
74 this permission notice appear in supporting documentation,
75 and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
76 used in advertising or publicity pertaining to distribution
77 of the software without specific, written prior permission.
78 M.I.T. and the M.I.T. S.I.P.B. make no representations about
79 the suitability of this software for any purpose.  It is
80 provided "as is" without express or implied warranty.
81 
82 */
83 
84 /*
85  *      newsyslog - roll over selected logs at the appropriate time,
86  *              keeping the a specified number of backup files around.
87  *
88  */
89 
90 #ifndef lint
91 static char rcsid[] = "$OpenBSD: newsyslog.c,v 1.37 2001/07/09 07:04:50 deraadt Exp $";
92 #endif /* not lint */
93 
94 #ifndef CONF
95 #define CONF "/etc/athena/newsyslog.conf" /* Configuration file */
96 #endif
97 #ifndef PIDFILE
98 #define PIDFILE "/etc/syslog.pid"
99 #endif
100 #ifndef COMPRESS
101 #define COMPRESS "/usr/ucb/compress" /* File compression program */
102 #endif
103 #ifndef COMPRESS_POSTFIX
104 #define COMPRESS_POSTFIX ".Z"
105 #endif
106 #ifndef STATS_DIR
107 #define STATS_DIR "/etc"
108 #endif
109 #ifndef SENDMAIL
110 #define SENDMAIL "/usr/lib/sendmail"
111 #endif
112 
113 #include <stdio.h>
114 #include <sys/types.h>
115 #include <sys/time.h>
116 #include <sys/stat.h>
117 #include <sys/param.h>
118 #include <sys/wait.h>
119 #include <stdlib.h>
120 #include <string.h>
121 #include <ctype.h>
122 #include <signal.h>
123 #include <fcntl.h>
124 #include <pwd.h>
125 #include <grp.h>
126 #include <unistd.h>
127 #include <err.h>
128 
129 #define CE_ROTATED	0x01		/* Log file has been rotated */
130 #define CE_COMPACT	0x02		/* Compact the achived log files */
131 #define CE_BINARY	0x04		/* Logfile is in binary, don't add */
132 					/* status messages */
133 #define CE_MONITOR	0x08		/* Monitory for changes */
134 #define NONE -1
135 
136 struct conf_entry {
137 	char    *log;		/* Name of the log */
138 	uid_t   uid;		/* Owner of log */
139 	gid_t   gid;		/* Group of log */
140 	int     numlogs;	/* Number of logs to keep */
141 	int     size;		/* Size cutoff to trigger trimming the log */
142 	int     hours;		/* Hours between log trimming */
143 	int     permissions;	/* File permissions on the log */
144 	int	signal;		/* Signal to send (defaults to SIGHUP) */
145 	int     flags;		/* Flags (CE_COMPACT & CE_BINARY)  */
146 	char	*whom;		/* Whom to notify if logfile changes */
147 	char	*pidfile;	/* Path to file containg pid to signal */
148 	char	*runcmd;	/* Command to run instead of sending a signal */
149 	struct conf_entry *next; /* Linked list pointer */
150 };
151 
152 struct pidinfo {
153 	char	*file;
154 	int	signal;
155 };
156 
157 int     verbose = 0;		/* Print out what's going on */
158 int     needroot = 1;		/* Root privs are necessary */
159 int     noaction = 0;		/* Don't do anything, just show it */
160 int	monitormode = 0;	/* Don't do monitoring by default */
161 char    *conf = CONF;		/* Configuration file to use */
162 time_t  timenow;
163 #define MIN_PID		4
164 char    hostname[MAXHOSTNAMELEN]; /* hostname */
165 char    *daytime;		/* timenow in human readable form */
166 
167 
168 void do_entry __P((struct conf_entry *));
169 void PRS __P((int, char **));
170 void usage __P((void));
171 struct conf_entry *parse_file __P((int *));
172 char *missing_field __P((char *, char *));
173 void dotrim __P((char *, int, int, int, uid_t, gid_t));
174 int log_trim __P((char *));
175 void compress_log __P((char *));
176 int sizefile __P((char *));
177 int age_old_log __P((char *));
178 char *sob __P((char *));
179 char *son __P((char *));
180 int isnumberstr __P((char *));
181 void domonitor __P((char *, char *));
182 FILE *openmail __P((void));
183 void closemail __P((FILE *));
184 void child_killer __P((int));
185 void run_command __P((char *));
186 void send_signal __P((char *, int));
187 
188 int
189 main(argc, argv)
190 	int argc;
191 	char **argv;
192 {
193 	struct conf_entry *p, *q;
194 	struct pidinfo *pidlist, *pl;
195 	int status, listlen;
196 
197 	PRS(argc, argv);
198 	if (needroot && getuid() && geteuid())
199 		errx(1, "You must be root.");
200 	p = q = parse_file(&listlen);
201 	signal(SIGCHLD, child_killer);
202 
203 	pidlist = (struct pidinfo *)calloc(sizeof(struct pidinfo), listlen + 1);
204 	if (pidlist == NULL)
205 		err(1, "calloc");
206 
207 	/* Step 1, rotate all log files */
208 	while (q) {
209 		do_entry(q);
210 		q = q->next;
211 	}
212 
213 	/* Step 2, make a list of unique pid files */
214 	for (q = p, pl = pidlist; q; ) {
215 		if (q->flags & CE_ROTATED) {
216 			struct pidinfo *pltmp;
217 
218 			for (pltmp = pidlist; pltmp < pl; pltmp++) {
219 				if ((strcmp(pltmp->file, q->pidfile) == 0 &&
220 				    pltmp->signal == q->signal) || (q->runcmd &&
221 				    strcmp(q->runcmd, pltmp->file) == 0))
222 					break;
223 			}
224 			if (pltmp == pl) {	/* unique entry */
225 				if (q->runcmd) {
226 					pl->file = q->runcmd;
227 					pl->signal = -1;
228 				} else {
229 					pl->file = q->pidfile;
230 					pl->signal = q->signal;
231 				}
232 				pl++;
233 			}
234 		}
235 		q = q->next;
236 	}
237 
238 	/* Step 3, send a signal or run a command */
239 	for (pl = pidlist; pl->file; pl++) {
240 		if (pl->signal == -1)
241 			run_command(pl->file);
242 		else
243 			send_signal(pl->file, pl->signal);
244 	}
245 	if (!noaction)
246 		sleep(5);
247 
248 	/* Step 4, compress the log.0 file if configured to do so and free */
249 	while (p) {
250 		if ((p->flags & CE_COMPACT) && (p->flags & CE_ROTATED))
251 			compress_log(p->log);
252 		q = p;
253 		p = p->next;
254 		free(q);
255 	}
256 
257 	/* Wait for children to finish, then exit */
258 	while (waitpid(-1, &status, 0) != -1)
259 		;
260 	exit(0);
261 }
262 
263 void
264 do_entry(ent)
265 	struct conf_entry       *ent;
266 
267 {
268 	int	modtime, size;
269 
270 	if (verbose)
271 		printf("%s <%d%s>: ", ent->log, ent->numlogs,
272 			(ent->flags & CE_COMPACT) ? "Z" : "");
273 	size = sizefile(ent->log);
274 	modtime = age_old_log(ent->log);
275 	if (size < 0) {
276 		if (verbose)
277 			printf("does not exist.\n");
278 	} else {
279 		if (verbose && (ent->size > 0))
280 			printf("size (Kb): %d [%d] ", size, ent->size);
281 		if (verbose && (ent->hours > 0))
282 			printf(" age (hr): %d [%d] ", modtime, ent->hours);
283 		if (monitormode && ent->flags & CE_MONITOR)
284 			domonitor(ent->log, ent->whom);
285 		if (!monitormode && (((ent->size > 0) && (size >= ent->size)) ||
286 		    ((ent->hours > 0) && ((modtime >= ent->hours)
287 					|| (modtime < 0))))) {
288 			if (verbose)
289 				printf("--> trimming log....\n");
290 			if (noaction && !verbose)
291 				printf("%s <%d%s>: ", ent->log, ent->numlogs,
292 					(ent->flags & CE_COMPACT) ? "Z" : "");
293 			dotrim(ent->log, ent->numlogs, ent->flags,
294 			       ent->permissions, ent->uid, ent->gid);
295 			ent->flags |= CE_ROTATED;
296 		} else if (verbose)
297 			printf("--> skipping\n");
298 	}
299 }
300 
301 /* Run the specified command */
302 void
303 run_command(cmd)
304 	char	*cmd;
305 {
306 
307 	if (noaction)
308 		(void)printf("run %s\n", cmd);
309 	else
310 		system(cmd);
311 }
312 
313 /* Send a signal to the pid specified by pidfile */
314 void
315 send_signal(pidfile, signal)
316 	char	*pidfile;
317 	int	signal;
318 {
319 	FILE	*f;
320 	char    line[BUFSIZ];
321 	pid_t	pid = 0;
322 
323 	if ((f = fopen(pidfile, "r")) == NULL) {
324 		warn("can't open %s", pidfile);
325 		return;
326 	}
327 
328 	if (fgets(line, sizeof(line), f))
329 		pid = atoi(line);
330 	(void)fclose(f);
331 
332 	if (noaction)
333 		(void)printf("kill -%s %d\n", sys_signame[signal], pid);
334 	else if (pid == 0)
335 		warnx("empty pid file: %s", pidfile);
336 	else if (pid < MIN_PID)
337 		warnx("preposterous process number: %d", pid);
338 	else if (kill(pid, signal))
339 		warnx("warning - could not send SIG%s to daemon",
340 		    sys_signame[signal]);
341 }
342 
343 void
344 PRS(argc, argv)
345 	int argc;
346 	char **argv;
347 {
348 	int     c;
349 	char	*p;
350 
351 	timenow = time(NULL);
352 	daytime = ctime(&timenow) + 4;
353 	daytime[15] = '\0';
354 
355 	/* Let's get our hostname */
356 	(void)gethostname(hostname, sizeof(hostname));
357 
358 	/* Truncate domain */
359 	p = strchr(hostname, '.');
360 	if (p)
361 		*p = '\0';
362 
363 	optind = 1;	     /* Start options parsing */
364 	while ((c = getopt(argc, argv, "nrvmf:")) != -1) {
365 		switch (c) {
366 		case 'n':
367 			noaction++; /* This implies needroot as off */
368 			/* fall through */
369 		case 'r':
370 			needroot = 0;
371 			break;
372 		case 'v':
373 			verbose++;
374 			break;
375 		case 'f':
376 			conf = optarg;
377 			break;
378 		case 'm':
379 			monitormode++;
380 			break;
381 		default:
382 			usage();
383 		}
384 	}
385 }
386 
387 void
388 usage()
389 {
390 	extern const char *__progname;
391 
392 	(void)fprintf(stderr, "usage: %s [-mnrv] [-f config_file]\n",
393 	    __progname);
394 	exit(1);
395 }
396 
397 /* Parse a configuration file and return a linked list of all the logs
398  * to process
399  */
400 struct conf_entry *
401 parse_file(nentries)
402 	int *nentries;
403 {
404 	FILE    *f;
405 	char    line[BUFSIZ], *parse, *q;
406 	char    *errline, *group, *tmp;
407 	struct conf_entry *first = NULL;
408 	struct conf_entry *working = NULL;
409 	struct passwd *pass;
410 	struct group *grp;
411 
412 	if (strcmp(conf, "-") == 0)
413 		f = stdin;
414 	else {
415 		if ((f = fopen(conf, "r")) == NULL)
416 			err(1, "can't open %s", conf);
417 	}
418 
419 	*nentries = 0;
420 	while (fgets(line, sizeof(line), f)) {
421 		if ((line[0] == '\n') || (line[0] == '#'))
422 			continue;
423 		errline = strdup(line);
424 		if (errline == NULL)
425 			err(1, "strdup");
426 		(*nentries)++;
427 		if (!first) {
428 			working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
429 			if (working == NULL)
430 				err(1, "malloc");
431 			first = working;
432 		} else {
433 			working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
434 			if (working->next == NULL)
435 				err(1, "malloc");
436 			working = working->next;
437 		}
438 
439 		q = parse = missing_field(sob(line), errline);
440 		*(parse = son(line)) = '\0';
441 		working->log = strdup(q);
442 		if (working->log == NULL)
443 			err(1, "strdup");
444 
445 		q = parse = missing_field(sob(++parse), errline);
446 		*(parse = son(parse)) = '\0';
447 		if ((group = strchr(q, '.')) != NULL) {
448 			*group++ = '\0';
449 			if (*q) {
450 				if (!(isnumberstr(q))) {
451 					if ((pass = getpwnam(q)) == NULL)
452 						errx(1, "Error in config file; unknown user: %s", q);
453 					working->uid = pass->pw_uid;
454 				} else
455 					working->uid = atoi(q);
456 			} else
457 				working->uid = NONE;
458 
459 			q = group;
460 			if (*q) {
461 				if (!(isnumberstr(q))) {
462 					if ((grp = getgrnam(q)) == NULL)
463 						errx(1, "Error in config file; unknown group: %s", q);
464 					working->gid = grp->gr_gid;
465 				} else
466 					working->gid = atoi(q);
467 			} else
468 				working->gid = NONE;
469 
470 			q = parse = missing_field(sob(++parse), errline);
471 			*(parse = son(parse)) = '\0';
472 		} else
473 			working->uid = working->gid = NONE;
474 
475 		if (!sscanf(q, "%o", &working->permissions))
476 			errx(1, "Error in config file; bad permissions: %s", q);
477 
478 		q = parse = missing_field(sob(++parse), errline);
479 		*(parse = son(parse)) = '\0';
480 		if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0)
481 			errx(1, "Error in config file; bad number: %s", q);
482 
483 		q = parse = missing_field(sob(++parse), errline);
484 		*(parse = son(parse)) = '\0';
485 		if (isdigit(*q))
486 			working->size = atoi(q);
487 		else
488 			working->size = -1;
489 
490 		q = parse = missing_field(sob(++parse), errline);
491 		*(parse = son(parse)) = '\0';
492 		if (isdigit(*q))
493 			working->hours = atoi(q);
494 		else
495 			working->hours = -1;
496 
497 		working->flags = 0;
498 		q = sob(++parse);	/* Optional field */
499 		if (*q == 'Z' || *q == 'z' || *q == 'B' || *q == 'b' ||
500 		    *q == 'M' || *q == 'm') {
501 			*(parse = son(q)) = '\0';
502 			while (*q) {
503 				switch (*q) {
504 				case 'Z':
505 				case 'z':
506 					working->flags |= CE_COMPACT;
507 					break;
508 				case 'B':
509 				case 'b':
510 					working->flags |= CE_BINARY;
511 					break;
512 				case 'M':
513 				case 'm':
514 					working->flags |= CE_MONITOR;
515 					break;
516 				default:
517 					errx(1, "Illegal flag in config file: %c", *q);
518 					break;
519 				}
520 				q++;
521 			}
522 		} else
523 		    parse--;	/* no flags so undo */
524 
525 		working->whom = NULL;
526 		if (working->flags & CE_MONITOR) {	/* Optional field */
527 			q = parse = sob(++parse);
528 			*(parse = son(parse)) = '\0';
529 
530 			working->whom = strdup(q);
531 			if (working->log == NULL)
532 				err(1, "strdup");
533 		}
534 
535 		working->pidfile = PIDFILE;
536 		working->signal = SIGHUP;
537 		working->runcmd = NULL;
538 		for (;;) {
539 			q = parse = sob(++parse);	/* Optional field */
540 			if (q == NULL || *q == '\0')
541 				break;
542 			if (*q == '/') {
543 				*(parse = son(parse)) = '\0';
544 				if (strlen(q) >= MAXPATHLEN)
545 					errx(1, "%s: pathname too long", q);
546 				working->pidfile = strdup(q);
547 				if (working->pidfile == NULL)
548 					err(1, "strdup");
549 			} else if (*q == '"' && (tmp = strchr(q + 1, '"'))) {
550 				*(parse = tmp) = '\0';
551 				working->runcmd = strdup(++q);
552 				if (working->runcmd == NULL)
553 					err(1, "strdup");
554 			} else if (strncmp(q, "SIG", 3) == 0) {
555 				int i;
556 
557 				*(parse = son(parse)) = '\0';
558 				for (i = 1; i < NSIG; i++) {
559 					if (!strcmp(sys_signame[i], q + 3)) {
560 						working->signal = i;
561 						break;
562 					}
563 				}
564 				if (i == NSIG)
565 					errx(1, "unknown signal: %s", q);
566 			} else
567 				errx(1, "unrecognized field: %s", q);
568 		}
569 
570 		/* Make sure we can't oflow MAXPATHLEN */
571 		if (asprintf(&tmp, "%s.%d%s", working->log, working->numlogs,
572 		    COMPRESS_POSTFIX) >= MAXPATHLEN)
573 			errx(1, "%s: pathname too long", working->log);
574 
575 		if (tmp)
576 			free(tmp);
577 		free(errline);
578 	}
579 	if (working)
580 		working->next = NULL;
581 	(void)fclose(f);
582 	return(first);
583 }
584 
585 char *
586 missing_field(p, errline)
587 	char    *p;
588 	char    *errline;
589 {
590 	if (!p || !*p) {
591 		warnx("Missing field in config file line:");
592 		fputs(errline, stderr);
593 		exit(1);
594 	}
595 	return(p);
596 }
597 
598 void
599 dotrim(log, numdays, flags, perm, owner_uid, group_gid)
600 	char    *log;
601 	int     numdays;
602 	int     flags;
603 	int     perm;
604 	uid_t   owner_uid;
605 	gid_t   group_gid;
606 {
607 	char    file1[MAXPATHLEN], file2[MAXPATHLEN];
608 	char    zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
609 	int     fd;
610 	struct  stat st;
611 	int	days = numdays;
612 
613 	/* Remove oldest log (may not exist) */
614 	(void)snprintf(file1, sizeof file1, "%s.%d", log, numdays);
615 	(void)snprintf(zfile1, sizeof zfile1, "%s.%d%s", log, numdays,
616 	    COMPRESS_POSTFIX);
617 
618 	if (noaction) {
619 		printf("rm -f %s %s\n", file1, zfile1);
620 	} else {
621 		(void)unlink(file1);
622 		(void)unlink(zfile1);
623 	}
624 
625 	/* Move down log files */
626 	while (numdays--) {
627 		(void)strlcpy(file2, file1, sizeof file2);
628 		(void)snprintf(file1, sizeof file1, "%s.%d", log, numdays);
629 		(void)strlcpy(zfile1, file1, sizeof zfile1);
630 		(void)strlcpy(zfile2, file2, sizeof zfile2);
631 		if (lstat(file1, &st)) {
632 			(void)strlcat(zfile1, COMPRESS_POSTFIX, sizeof zfile1);
633 			(void)strlcat(zfile2, COMPRESS_POSTFIX, sizeof zfile2);
634 			if (lstat(zfile1, &st))
635 				continue;
636 		}
637 		if (noaction) {
638 			printf("mv %s %s\n", zfile1, zfile2);
639 			printf("chmod %o %s\n", perm, zfile2);
640 			printf("chown %d:%d %s\n",
641 			       owner_uid, group_gid, zfile2);
642 		} else {
643 			if (rename(zfile1, zfile2))
644 				warn("can't mv %s to %s", zfile1, zfile2);
645 			if (chmod(zfile2, perm))
646 				warn("can't chmod %s", zfile2);
647 			if (chown(zfile2, owner_uid, group_gid))
648 				warn("can't chown %s", zfile2);
649 		}
650 	}
651 	if (!noaction && !(flags & CE_BINARY))
652 		(void)log_trim(log);  /* Report the trimming to the old log */
653 
654 	(void)snprintf(file2, sizeof(file2), "%s.XXXXXXXXXX", log);
655 	if (noaction)  {
656 		printf("Create new log file...\n");
657 	} else {
658 		if ((fd = mkstemp(file2)) < 0)
659 			err(1, "can't start '%s' log", file2);
660 		if (fchown(fd, owner_uid, group_gid))
661 			err(1, "can't chown '%s' log file", file2);
662 		if (fchmod(fd, perm))
663 			err(1, "can't chmod '%s' log file", file2);
664 		(void)close(fd);
665 		/* Add status message */
666 		if (!(flags & CE_BINARY) && log_trim(file2))
667 			err(1, "can't add status message to log '%s'", file2);
668 	}
669 
670 	if (days == 0) {
671 		if (noaction)
672 			printf("rm %s\n", log);
673 		else if (unlink(log))
674 			warn("can't rm %s", log);
675 	} else {
676 		if (noaction)
677 			printf("mv %s to %s\n", log, file1);
678 		else if (rename(log, file1))
679 			warn("can't to mv %s to %s", log, file1);
680 	}
681 
682 	/* Now move the new log file into place */
683 	if (noaction)
684 		printf("mv %s to %s\n", file2, log);
685 	else if (rename(file2, log))
686 		warn("can't to mv %s to %s", file2, log);
687 }
688 
689 /* Log the fact that the logs were turned over */
690 int
691 log_trim(log)
692 	char    *log;
693 {
694 	FILE    *f;
695 
696 	if ((f = fopen(log, "a")) == NULL)
697 		return(-1);
698 	(void)fprintf(f, "%s %s newsyslog[%d]: logfile turned over\n",
699 	    daytime, hostname, getpid());
700 	if (fclose(f) == EOF)
701 		err(1, "log_trim: fclose");
702 	return(0);
703 }
704 
705 /* Fork off compress or gzip to compress the old log file */
706 void
707 compress_log(log)
708 	char    *log;
709 {
710 	pid_t   pid;
711 	char	*base;
712 	char    tmp[MAXPATHLEN];
713 
714 	if ((base = strrchr(COMPRESS, '/')) == NULL)
715 		base = COMPRESS;
716 	else
717 		base++;
718 	if (noaction) {
719 		printf("%s %s.0\n", base, log);
720 		return;
721 	}
722 	pid = fork();
723 	(void)snprintf(tmp, sizeof tmp, "%s.0", log);
724 	if (pid < 0) {
725 		err(1, "fork");
726 	} else if (!pid) {
727 		(void)execl(COMPRESS, base, "-f", tmp, (char *)NULL);
728 		warn(COMPRESS);
729 		_exit(1);
730 	}
731 }
732 
733 /* Return size in kilobytes of a file */
734 int
735 sizefile(file)
736 	char    *file;
737 {
738 	struct stat sb;
739 
740 	if (stat(file, &sb) < 0)
741 		return(-1);
742 	return(sb.st_blocks / (1024.0 / DEV_BSIZE));
743 }
744 
745 /* Return the age (in hours) of old log file (file.0), or -1 if none */
746 int
747 age_old_log(file)
748 	char    *file;
749 {
750 	struct stat sb;
751 	char tmp[MAXPATHLEN];
752 
753 	(void)strlcpy(tmp, file, sizeof tmp);
754 	strlcat(tmp, ".0", sizeof tmp);
755 	if (stat(tmp, &sb) < 0) {
756 		strlcat(tmp, COMPRESS_POSTFIX, sizeof tmp);
757 		if (stat(tmp, &sb) < 0)
758 			return(-1);
759 	}
760 	return( (int) (timenow - sb.st_mtime + 1800) / 3600);
761 }
762 
763 /* Skip Over Blanks */
764 char *
765 sob(p)
766 	register char   *p;
767 {
768 	while (p && *p && isspace(*p))
769 		p++;
770 	return(p);
771 }
772 
773 /* Skip Over Non-Blanks */
774 char *
775 son(p)
776 	register char   *p;
777 {
778 	while (p && *p && !isspace(*p))
779 		p++;
780 	return(p);
781 }
782 
783 /* Check if string is actually a number */
784 int
785 isnumberstr(string)
786 	char *string;
787 {
788 	while (*string) {
789 		if (!isdigit(*string++))
790 			return(0);
791 	}
792 	return(1);
793 }
794 
795 void
796 domonitor(log, whom)
797 	char *log, *whom;
798 {
799 	struct stat sb, tsb;
800 	char fname[MAXPATHLEN], *flog, *p, *rb = NULL;
801 	FILE *fp;
802 	off_t osize;
803 	int rd;
804 
805 	if (stat(log, &sb) < 0)
806 		return;
807 
808 	flog = strdup(log);
809 	if (flog == NULL)
810 		err(1, "strdup");
811 
812 	for (p = flog; *p != '\0'; p++) {
813 		if (*p == '/')
814 			*p = '_';
815 	}
816 	snprintf(fname, sizeof fname, "%s/newsyslog.%s.size",
817 	    STATS_DIR, flog);
818 
819 	/* ..if it doesn't exist, simply record the current size. */
820 	if ((sb.st_size == 0) || stat(fname, &tsb) < 0)
821 		goto update;
822 
823 	fp = fopen(fname, "r");
824 	if (fp == NULL) {
825 		warn("%s", fname);
826 		goto cleanup;
827 	}
828 #ifdef QUAD_OFF_T
829 	if (fscanf(fp, "%qd\n", &osize) != 1) {
830 #else
831 	if (fscanf(fp, "%ld\n", &osize) != 1) {
832 #endif	/* QUAD_OFF_T */
833 		fclose(fp);
834 		goto update;
835 	}
836 
837 	fclose(fp);
838 
839 	/* If the file is smaller, mark the entire thing as changed. */
840 	if (sb.st_size < osize)
841 		osize = 0;
842 
843 	/* Now see if current size is larger. */
844 	if (sb.st_size > osize) {
845 		rb = (char *) malloc(sb.st_size - osize);
846 		if (rb == NULL)
847 			err(1, "malloc");
848 
849 		/* Open logfile, seek. */
850 		fp = fopen(log, "r");
851 		if (fp == NULL) {
852 			warn("%s", log);
853 			goto cleanup;
854 		}
855 		fseek(fp, osize, SEEK_SET);
856 		rd = fread(rb, 1, sb.st_size - osize, fp);
857 		if (rd < 1) {
858 			warn("fread");
859 			fclose(fp);
860 			goto cleanup;
861 		}
862 
863 		/* Send message. */
864 		fclose(fp);
865 
866 		fp = openmail();
867 		if (fp == NULL) {
868 			warn("openmail");
869 			goto cleanup;
870 		}
871 		fprintf(fp, "To: %s\nSubject: LOGFILE NOTIFICATION: %s\n\n\n",
872 		    whom, log);
873 		fwrite(rb, 1, rd, fp);
874 		fputs("\n\n", fp);
875 
876 		closemail(fp);
877 	}
878 update:
879 	/* Reopen for writing and update file. */
880 	fp = fopen(fname, "w");
881 	if (fp == NULL) {
882 		warn("%s", fname);
883 		goto cleanup;
884 	}
885 #ifdef QUAD_OFF_T
886 	fprintf(fp, "%qd\n", sb.st_size);
887 #else
888 	fprintf(fp, "%ld\n", sb.st_size);
889 #endif	/* QUAD_OFF_T */
890 	fclose(fp);
891 
892 cleanup:
893 	free(flog);
894 	if (rb != NULL)
895 		free(rb);
896 }
897 
898 FILE *
899 openmail()
900 {
901 	char *cmdbuf = NULL;
902 	FILE *ret;
903 
904 	asprintf(&cmdbuf, "%s -t", SENDMAIL);
905 	if (cmdbuf) {
906 		ret = popen(cmdbuf, "w");
907 		free(cmdbuf);
908 		return (ret);
909 	}
910 	return (NULL);
911 }
912 
913 void
914 closemail(pfp)
915 	FILE *pfp;
916 {
917 	pclose(pfp);
918 }
919 
920 void
921 child_killer(signum)
922 	int signum;
923 {
924 	int status;
925 
926 	while (waitpid(-1, &status, WNOHANG) > 0)
927 		;
928 }
929