1 |
|
|
/* $OpenBSD: newsyslog.c,v 1.101 2016/06/01 16:57:48 tedu Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 1999, 2002, 2003 Todd C. Miller <Todd.Miller@courtesan.com> |
5 |
|
|
* |
6 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
7 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
8 |
|
|
* copyright notice and this permission notice appear in all copies. |
9 |
|
|
* |
10 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 |
|
|
* |
18 |
|
|
* Sponsored in part by the Defense Advanced Research Projects |
19 |
|
|
* Agency (DARPA) and Air Force Research Laboratory, Air Force |
20 |
|
|
* Materiel Command, USAF, under agreement number F39502-99-1-0512. |
21 |
|
|
*/ |
22 |
|
|
|
23 |
|
|
/* |
24 |
|
|
* Copyright (c) 1997, Jason Downs. All rights reserved. |
25 |
|
|
* |
26 |
|
|
* Redistribution and use in source and binary forms, with or without |
27 |
|
|
* modification, are permitted provided that the following conditions |
28 |
|
|
* are met: |
29 |
|
|
* 1. Redistributions of source code must retain the above copyright |
30 |
|
|
* notice, this list of conditions and the following disclaimer. |
31 |
|
|
* 2. Redistributions in binary form must reproduce the above copyright |
32 |
|
|
* notice, this list of conditions and the following disclaimer in the |
33 |
|
|
* documentation and/or other materials provided with the distribution. |
34 |
|
|
* |
35 |
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS |
36 |
|
|
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
37 |
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
38 |
|
|
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, |
39 |
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
40 |
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
41 |
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
42 |
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
43 |
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
44 |
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
45 |
|
|
* SUCH DAMAGE. |
46 |
|
|
*/ |
47 |
|
|
|
48 |
|
|
/* |
49 |
|
|
* This file contains changes from the Open Software Foundation. |
50 |
|
|
*/ |
51 |
|
|
|
52 |
|
|
/* |
53 |
|
|
* Copyright 1988, 1989 by the Massachusetts Institute of Technology |
54 |
|
|
* |
55 |
|
|
* Permission to use, copy, modify, and distribute this software |
56 |
|
|
* and its documentation for any purpose and without fee is |
57 |
|
|
* hereby granted, provided that the above copyright notice |
58 |
|
|
* appear in all copies and that both that copyright notice and |
59 |
|
|
* this permission notice appear in supporting documentation, |
60 |
|
|
* and that the names of M.I.T. and the M.I.T. S.I.P.B. not be |
61 |
|
|
* used in advertising or publicity pertaining to distribution |
62 |
|
|
* of the software without specific, written prior permission. |
63 |
|
|
* M.I.T. and the M.I.T. S.I.P.B. make no representations about |
64 |
|
|
* the suitability of this software for any purpose. It is |
65 |
|
|
* provided "as is" without express or implied warranty. |
66 |
|
|
*/ |
67 |
|
|
|
68 |
|
|
/* |
69 |
|
|
* newsyslog - roll over selected logs at the appropriate time, |
70 |
|
|
* keeping the specified number of backup files around. |
71 |
|
|
* |
72 |
|
|
*/ |
73 |
|
|
|
74 |
|
|
#define CONF "/etc/newsyslog.conf" |
75 |
|
|
#define PIDFILE "/var/run/syslog.pid" |
76 |
|
|
#define COMPRESS "/usr/bin/gzip" |
77 |
|
|
#define COMPRESS_POSTFIX ".gz" |
78 |
|
|
#define STATS_DIR "/var/run" |
79 |
|
|
#define SENDMAIL "/usr/sbin/sendmail" |
80 |
|
|
|
81 |
|
|
#include <sys/param.h> /* DEV_BSIZE */ |
82 |
|
|
#include <sys/stat.h> |
83 |
|
|
#include <sys/time.h> |
84 |
|
|
#include <sys/wait.h> |
85 |
|
|
|
86 |
|
|
#include <ctype.h> |
87 |
|
|
#include <err.h> |
88 |
|
|
#include <errno.h> |
89 |
|
|
#include <fcntl.h> |
90 |
|
|
#include <grp.h> |
91 |
|
|
#include <limits.h> |
92 |
|
|
#include <pwd.h> |
93 |
|
|
#include <signal.h> |
94 |
|
|
#include <stdio.h> |
95 |
|
|
#include <stdlib.h> |
96 |
|
|
#include <string.h> |
97 |
|
|
#include <time.h> |
98 |
|
|
#include <unistd.h> |
99 |
|
|
|
100 |
|
|
#define CE_ROTATED 0x01 /* Log file has been rotated */ |
101 |
|
|
#define CE_COMPACT 0x02 /* Compact the archived log files */ |
102 |
|
|
#define CE_BINARY 0x04 /* Logfile is in binary, don't add */ |
103 |
|
|
/* status messages */ |
104 |
|
|
#define CE_MONITOR 0x08 /* Monitor for changes */ |
105 |
|
|
#define CE_FOLLOW 0x10 /* Follow symbolic links */ |
106 |
|
|
#define CE_TRIMAT 0x20 /* Trim at a specific time */ |
107 |
|
|
|
108 |
|
|
#define MIN_PID 2 /* Don't touch pids lower than this */ |
109 |
|
|
#define MIN_SIZE 256 /* Don't rotate if smaller (in bytes) */ |
110 |
|
|
|
111 |
|
|
#define DPRINTF(x) do { if (verbose) printf x ; } while (0) |
112 |
|
|
|
113 |
|
|
struct conf_entry { |
114 |
|
|
char *log; /* Name of the log */ |
115 |
|
|
char *logbase; /* Basename of the log */ |
116 |
|
|
char *backdir; /* Directory in which to store backups */ |
117 |
|
|
uid_t uid; /* Owner of log */ |
118 |
|
|
gid_t gid; /* Group of log */ |
119 |
|
|
int numlogs; /* Number of logs to keep */ |
120 |
|
|
off_t size; /* Size cutoff to trigger trimming the log */ |
121 |
|
|
int hours; /* Hours between log trimming */ |
122 |
|
|
time_t trim_at; /* Specific time at which to do trimming */ |
123 |
|
|
mode_t permissions; /* File permissions on the log */ |
124 |
|
|
int signal; /* Signal to send (defaults to SIGHUP) */ |
125 |
|
|
int flags; /* Flags (CE_COMPACT & CE_BINARY) */ |
126 |
|
|
char *whom; /* Whom to notify if logfile changes */ |
127 |
|
|
char *pidfile; /* Path to file containing pid to signal */ |
128 |
|
|
char *runcmd; /* Command to run instead of sending a signal */ |
129 |
|
|
struct conf_entry *next; /* Linked list pointer */ |
130 |
|
|
}; |
131 |
|
|
|
132 |
|
|
struct pidinfo { |
133 |
|
|
char *file; |
134 |
|
|
int signal; |
135 |
|
|
}; |
136 |
|
|
|
137 |
|
|
int verbose = 0; /* Print out what's going on */ |
138 |
|
|
int needroot = 1; /* Root privs are necessary */ |
139 |
|
|
int noaction = 0; /* Don't do anything, just show it */ |
140 |
|
|
int monitormode = 0; /* Don't do monitoring by default */ |
141 |
|
|
int force = 0; /* Force the logs to be rotated */ |
142 |
|
|
char *conf = CONF; /* Configuration file to use */ |
143 |
|
|
time_t timenow; |
144 |
|
|
char hostname[HOST_NAME_MAX+1]; /* Hostname */ |
145 |
|
|
char *daytime; /* timenow in human readable form */ |
146 |
|
|
char *arcdir; /* Dir to put archives in (if it exists) */ |
147 |
|
|
|
148 |
|
|
FILE *openmail(void); |
149 |
|
|
char *lstat_log(char *, size_t, int); |
150 |
|
|
char *missing_field(char *, char *, int); |
151 |
|
|
char *sob(char *); |
152 |
|
|
char *son(char *); |
153 |
|
|
int age_old_log(struct conf_entry *); |
154 |
|
|
int domonitor(struct conf_entry *); |
155 |
|
|
int isnumberstr(char *); |
156 |
|
|
int log_trim(char *); |
157 |
|
|
int movefile(char *, char *, uid_t, gid_t, mode_t); |
158 |
|
|
int stat_suffix(char *, size_t, char *, struct stat *, |
159 |
|
|
int (*)(const char *, struct stat *)); |
160 |
|
|
off_t sizefile(struct stat *); |
161 |
|
|
struct conf_entry * |
162 |
|
|
parse_file(int *); |
163 |
|
|
time_t parse8601(char *); |
164 |
|
|
time_t parseDWM(char *); |
165 |
|
|
void child_killer(int); |
166 |
|
|
void compress_log(struct conf_entry *); |
167 |
|
|
void do_entry(struct conf_entry *); |
168 |
|
|
void dotrim(struct conf_entry *); |
169 |
|
|
void rotate(struct conf_entry *, const char *); |
170 |
|
|
void parse_args(int, char **); |
171 |
|
|
void run_command(char *); |
172 |
|
|
void send_signal(char *, int); |
173 |
|
|
void usage(void); |
174 |
|
|
|
175 |
|
|
int |
176 |
|
|
main(int argc, char **argv) |
177 |
|
|
{ |
178 |
|
|
struct conf_entry *p, *q, *x, *y; |
179 |
|
|
struct pidinfo *pidlist, *pl; |
180 |
|
|
int status, listlen; |
181 |
|
|
char **av; |
182 |
|
|
|
183 |
|
|
parse_args(argc, argv); |
184 |
|
|
argc -= optind; |
185 |
|
|
argv += optind; |
186 |
|
|
|
187 |
|
|
if (needroot && getuid() && geteuid()) |
188 |
|
|
errx(1, "You must be root."); |
189 |
|
|
|
190 |
|
|
p = parse_file(&listlen); |
191 |
|
|
if (argc > 0) { |
192 |
|
|
/* Only rotate specified files. */ |
193 |
|
|
x = y = NULL; |
194 |
|
|
listlen = 0; |
195 |
|
|
for (av = argv; *av; av++) { |
196 |
|
|
for (q = p; q; q = q->next) |
197 |
|
|
if (strcmp(*av, q->log) == 0) { |
198 |
|
|
if (x == NULL) |
199 |
|
|
x = y = q; |
200 |
|
|
else { |
201 |
|
|
y->next = q; |
202 |
|
|
y = q; |
203 |
|
|
} |
204 |
|
|
listlen++; |
205 |
|
|
break; |
206 |
|
|
} |
207 |
|
|
if (q == NULL) |
208 |
|
|
warnx("%s: %s not found", conf, *av); |
209 |
|
|
} |
210 |
|
|
if (x == NULL) |
211 |
|
|
errx(1, "%s: no specified log files", conf); |
212 |
|
|
y->next = NULL; |
213 |
|
|
p = x; |
214 |
|
|
} |
215 |
|
|
|
216 |
|
|
pidlist = calloc(listlen + 1, sizeof(struct pidinfo)); |
217 |
|
|
if (pidlist == NULL) |
218 |
|
|
err(1, "calloc"); |
219 |
|
|
|
220 |
|
|
signal(SIGCHLD, child_killer); |
221 |
|
|
|
222 |
|
|
/* Step 1, rotate all log files */ |
223 |
|
|
for (q = p; q; q = q->next) |
224 |
|
|
do_entry(q); |
225 |
|
|
|
226 |
|
|
/* Step 2, make a list of unique pid files */ |
227 |
|
|
for (q = p, pl = pidlist; q; ) { |
228 |
|
|
if (q->flags & CE_ROTATED) { |
229 |
|
|
struct pidinfo *pltmp; |
230 |
|
|
|
231 |
|
|
for (pltmp = pidlist; pltmp < pl; pltmp++) { |
232 |
|
|
if ((q->pidfile && pltmp->file && |
233 |
|
|
strcmp(pltmp->file, q->pidfile) == 0 && |
234 |
|
|
pltmp->signal == q->signal) || |
235 |
|
|
(q->runcmd && pltmp->file && |
236 |
|
|
strcmp(q->runcmd, pltmp->file) == 0)) |
237 |
|
|
break; |
238 |
|
|
} |
239 |
|
|
if (pltmp == pl) { /* unique entry */ |
240 |
|
|
if (q->runcmd) { |
241 |
|
|
pl->file = q->runcmd; |
242 |
|
|
pl->signal = -1; |
243 |
|
|
} else { |
244 |
|
|
pl->file = q->pidfile; |
245 |
|
|
pl->signal = q->signal; |
246 |
|
|
} |
247 |
|
|
pl++; |
248 |
|
|
} |
249 |
|
|
} |
250 |
|
|
q = q->next; |
251 |
|
|
} |
252 |
|
|
|
253 |
|
|
/* Step 3, send a signal or run a command */ |
254 |
|
|
for (pl--; pl >= pidlist; pl--) { |
255 |
|
|
if (pl->file != NULL) { |
256 |
|
|
if (pl->signal == -1) |
257 |
|
|
run_command(pl->file); |
258 |
|
|
else |
259 |
|
|
send_signal(pl->file, pl->signal); |
260 |
|
|
} |
261 |
|
|
} |
262 |
|
|
if (!noaction) |
263 |
|
|
sleep(5); |
264 |
|
|
|
265 |
|
|
/* Step 4, compress the log.0 file if configured to do so and free */ |
266 |
|
|
while (p) { |
267 |
|
|
if ((p->flags & CE_COMPACT) && (p->flags & CE_ROTATED) && |
268 |
|
|
p->numlogs > 0) |
269 |
|
|
compress_log(p); |
270 |
|
|
q = p; |
271 |
|
|
p = p->next; |
272 |
|
|
free(q); |
273 |
|
|
} |
274 |
|
|
|
275 |
|
|
/* Wait for children to finish, then exit */ |
276 |
|
|
while (waitpid(-1, &status, 0) != -1) |
277 |
|
|
; |
278 |
|
|
exit(0); |
279 |
|
|
} |
280 |
|
|
|
281 |
|
|
void |
282 |
|
|
do_entry(struct conf_entry *ent) |
283 |
|
|
{ |
284 |
|
|
struct stat sb; |
285 |
|
|
int modhours; |
286 |
|
|
off_t size; |
287 |
|
|
|
288 |
|
|
if (lstat(ent->log, &sb) != 0) |
289 |
|
|
return; |
290 |
|
|
if (!S_ISREG(sb.st_mode) && |
291 |
|
|
(!S_ISLNK(sb.st_mode) || !(ent->flags & CE_FOLLOW))) { |
292 |
|
|
DPRINTF(("--> not a regular file, skipping\n")); |
293 |
|
|
return; |
294 |
|
|
} |
295 |
|
|
if (S_ISLNK(sb.st_mode) && stat(ent->log, &sb) != 0) { |
296 |
|
|
DPRINTF(("--> link target does not exist, skipping\n")); |
297 |
|
|
return; |
298 |
|
|
} |
299 |
|
|
if (ent->uid == (uid_t)-1) |
300 |
|
|
ent->uid = sb.st_uid; |
301 |
|
|
if (ent->gid == (gid_t)-1) |
302 |
|
|
ent->gid = sb.st_gid; |
303 |
|
|
|
304 |
|
|
DPRINTF(("%s <%d%s%s%s%s>: ", ent->log, ent->numlogs, |
305 |
|
|
(ent->flags & CE_COMPACT) ? "Z" : "", |
306 |
|
|
(ent->flags & CE_BINARY) ? "B" : "", |
307 |
|
|
(ent->flags & CE_FOLLOW) ? "F" : "", |
308 |
|
|
(ent->flags & CE_MONITOR) && monitormode ? "M" : "")); |
309 |
|
|
size = sizefile(&sb); |
310 |
|
|
modhours = age_old_log(ent); |
311 |
|
|
if (ent->flags & CE_TRIMAT && !force) { |
312 |
|
|
if (timenow < ent->trim_at || |
313 |
|
|
difftime(timenow, ent->trim_at) >= 60 * 60) { |
314 |
|
|
DPRINTF(("--> will trim at %s", |
315 |
|
|
ctime(&ent->trim_at))); |
316 |
|
|
return; |
317 |
|
|
} else if (ent->hours <= 0) { |
318 |
|
|
DPRINTF(("--> time is up\n")); |
319 |
|
|
} |
320 |
|
|
} |
321 |
|
|
if (ent->size > 0) |
322 |
|
|
DPRINTF(("size (KB): %.2f [%d] ", size / 1024.0, |
323 |
|
|
(int)(ent->size / 1024))); |
324 |
|
|
if (ent->hours > 0) |
325 |
|
|
DPRINTF(("age (hr): %d [%d] ", modhours, ent->hours)); |
326 |
|
|
if (monitormode && (ent->flags & CE_MONITOR) && domonitor(ent)) |
327 |
|
|
DPRINTF(("--> monitored\n")); |
328 |
|
|
else if (!monitormode && |
329 |
|
|
(force || (ent->size > 0 && size >= ent->size) || |
330 |
|
|
(ent->hours <= 0 && (ent->flags & CE_TRIMAT)) || |
331 |
|
|
(ent->hours > 0 && (modhours >= ent->hours || modhours < 0) |
332 |
|
|
&& ((ent->flags & CE_BINARY) || size >= MIN_SIZE)))) { |
333 |
|
|
DPRINTF(("--> trimming log....\n")); |
334 |
|
|
if (noaction && !verbose) |
335 |
|
|
printf("%s <%d%s%s%s>\n", ent->log, |
336 |
|
|
ent->numlogs, |
337 |
|
|
(ent->flags & CE_COMPACT) ? "Z" : "", |
338 |
|
|
(ent->flags & CE_BINARY) ? "B" : "", |
339 |
|
|
(ent->flags & CE_FOLLOW) ? "F" : ""); |
340 |
|
|
dotrim(ent); |
341 |
|
|
ent->flags |= CE_ROTATED; |
342 |
|
|
} else |
343 |
|
|
DPRINTF(("--> skipping\n")); |
344 |
|
|
} |
345 |
|
|
|
346 |
|
|
/* Run the specified command */ |
347 |
|
|
void |
348 |
|
|
run_command(char *cmd) |
349 |
|
|
{ |
350 |
|
|
if (noaction) |
351 |
|
|
(void)printf("run %s\n", cmd); |
352 |
|
|
else |
353 |
|
|
system(cmd); |
354 |
|
|
} |
355 |
|
|
|
356 |
|
|
/* Send a signal to the pid specified by pidfile */ |
357 |
|
|
void |
358 |
|
|
send_signal(char *pidfile, int signal) |
359 |
|
|
{ |
360 |
|
|
char line[BUFSIZ], *ep, *err; |
361 |
|
|
pid_t pid; |
362 |
|
|
long lval; |
363 |
|
|
FILE *f; |
364 |
|
|
|
365 |
|
|
if ((f = fopen(pidfile, "r")) == NULL) { |
366 |
|
|
warn("can't open %s", pidfile); |
367 |
|
|
return; |
368 |
|
|
} |
369 |
|
|
|
370 |
|
|
pid = 0; |
371 |
|
|
errno = 0; |
372 |
|
|
err = NULL; |
373 |
|
|
if (fgets(line, sizeof(line), f)) { |
374 |
|
|
lval = strtol(line, &ep, 10); |
375 |
|
|
if (line[0] == '\0' || (*ep != '\0' && *ep != '\n')) |
376 |
|
|
err = "invalid number in"; |
377 |
|
|
else if (lval < 0 || (errno == ERANGE && lval == LONG_MAX)) |
378 |
|
|
err = "out of range number in"; |
379 |
|
|
else if (lval == 0) |
380 |
|
|
err = "no number in"; |
381 |
|
|
else if (lval < MIN_PID) |
382 |
|
|
err = "preposterous process number in"; |
383 |
|
|
else |
384 |
|
|
pid = (pid_t)lval; |
385 |
|
|
} else { |
386 |
|
|
if (errno == 0) |
387 |
|
|
err = "empty"; |
388 |
|
|
else |
389 |
|
|
err = "error reading"; |
390 |
|
|
} |
391 |
|
|
(void)fclose(f); |
392 |
|
|
|
393 |
|
|
if (err) |
394 |
|
|
warnx("%s pid file: %s", err, pidfile); |
395 |
|
|
else if (noaction) |
396 |
|
|
(void)printf("kill -%s %ld\n", sys_signame[signal], (long)pid); |
397 |
|
|
else if (kill(pid, signal)) |
398 |
|
|
warnx("warning - could not send SIG%s to PID from pid file %s", |
399 |
|
|
sys_signame[signal], pidfile); |
400 |
|
|
} |
401 |
|
|
|
402 |
|
|
void |
403 |
|
|
parse_args(int argc, char **argv) |
404 |
|
|
{ |
405 |
|
|
char *p; |
406 |
|
|
int ch; |
407 |
|
|
|
408 |
|
|
timenow = time(NULL); |
409 |
|
|
daytime = ctime(&timenow) + 4; |
410 |
|
|
daytime[15] = '\0'; |
411 |
|
|
|
412 |
|
|
/* Let's get our hostname */ |
413 |
|
|
(void)gethostname(hostname, sizeof(hostname)); |
414 |
|
|
|
415 |
|
|
/* Truncate domain */ |
416 |
|
|
if ((p = strchr(hostname, '.')) != NULL) |
417 |
|
|
*p = '\0'; |
418 |
|
|
|
419 |
|
|
while ((ch = getopt(argc, argv, "Fmnrva:f:")) != -1) { |
420 |
|
|
switch (ch) { |
421 |
|
|
case 'a': |
422 |
|
|
arcdir = optarg; |
423 |
|
|
break; |
424 |
|
|
case 'n': |
425 |
|
|
noaction = 1; /* This implies needroot as off */ |
426 |
|
|
/* fall through */ |
427 |
|
|
case 'r': |
428 |
|
|
needroot = 0; |
429 |
|
|
break; |
430 |
|
|
case 'v': |
431 |
|
|
verbose = 1; |
432 |
|
|
break; |
433 |
|
|
case 'f': |
434 |
|
|
conf = optarg; |
435 |
|
|
break; |
436 |
|
|
case 'm': |
437 |
|
|
monitormode = 1; |
438 |
|
|
break; |
439 |
|
|
case 'F': |
440 |
|
|
force = 1; |
441 |
|
|
break; |
442 |
|
|
default: |
443 |
|
|
usage(); |
444 |
|
|
} |
445 |
|
|
} |
446 |
|
|
if (monitormode && force) |
447 |
|
|
errx(1, "cannot specify both -m and -F flags"); |
448 |
|
|
} |
449 |
|
|
|
450 |
|
|
void |
451 |
|
|
usage(void) |
452 |
|
|
{ |
453 |
|
|
extern const char *__progname; |
454 |
|
|
|
455 |
|
|
(void)fprintf(stderr, "usage: %s [-Fmnrv] [-a directory] " |
456 |
|
|
"[-f config_file] [log ...]\n", __progname); |
457 |
|
|
exit(1); |
458 |
|
|
} |
459 |
|
|
|
460 |
|
|
/* |
461 |
|
|
* Parse a configuration file and return a linked list of all the logs |
462 |
|
|
* to process |
463 |
|
|
*/ |
464 |
|
|
struct conf_entry * |
465 |
|
|
parse_file(int *nentries) |
466 |
|
|
{ |
467 |
|
|
char line[BUFSIZ], *parse, *q, *errline, *group, *tmp, *ep; |
468 |
|
|
struct conf_entry *working = NULL, *first = NULL; |
469 |
|
|
struct passwd *pwd; |
470 |
|
|
struct group *grp; |
471 |
|
|
struct stat sb; |
472 |
|
|
int lineno; |
473 |
|
|
FILE *f; |
474 |
|
|
long l; |
475 |
|
|
|
476 |
|
|
if (strcmp(conf, "-") == 0) |
477 |
|
|
f = stdin; |
478 |
|
|
else if ((f = fopen(conf, "r")) == NULL) |
479 |
|
|
err(1, "can't open %s", conf); |
480 |
|
|
|
481 |
|
|
*nentries = 0; |
482 |
|
|
for (lineno = 1; fgets(line, sizeof(line), f); lineno++) { |
483 |
|
|
tmp = sob(line); |
484 |
|
|
if (*tmp == '\0' || *tmp == '#') |
485 |
|
|
continue; |
486 |
|
|
errline = strdup(tmp); |
487 |
|
|
if (errline == NULL) |
488 |
|
|
err(1, "strdup"); |
489 |
|
|
(*nentries)++; |
490 |
|
|
if (!first) { |
491 |
|
|
working = malloc(sizeof(struct conf_entry)); |
492 |
|
|
if (working == NULL) |
493 |
|
|
err(1, "malloc"); |
494 |
|
|
first = working; |
495 |
|
|
} else { |
496 |
|
|
working->next = malloc(sizeof(struct conf_entry)); |
497 |
|
|
if (working->next == NULL) |
498 |
|
|
err(1, "malloc"); |
499 |
|
|
working = working->next; |
500 |
|
|
} |
501 |
|
|
|
502 |
|
|
q = parse = missing_field(sob(line), errline, lineno); |
503 |
|
|
*(parse = son(line)) = '\0'; |
504 |
|
|
working->log = strdup(q); |
505 |
|
|
if (working->log == NULL) |
506 |
|
|
err(1, "strdup"); |
507 |
|
|
|
508 |
|
|
if ((working->logbase = strrchr(working->log, '/')) != NULL) |
509 |
|
|
working->logbase++; |
510 |
|
|
|
511 |
|
|
q = parse = missing_field(sob(++parse), errline, lineno); |
512 |
|
|
*(parse = son(parse)) = '\0'; |
513 |
|
|
if ((group = strchr(q, ':')) != NULL || |
514 |
|
|
(group = strrchr(q, '.')) != NULL) { |
515 |
|
|
*group++ = '\0'; |
516 |
|
|
if (*q) { |
517 |
|
|
if (!(isnumberstr(q))) { |
518 |
|
|
if ((pwd = getpwnam(q)) == NULL) |
519 |
|
|
errx(1, "%s:%d: unknown user: %s", |
520 |
|
|
conf, lineno, q); |
521 |
|
|
working->uid = pwd->pw_uid; |
522 |
|
|
} else |
523 |
|
|
working->uid = atoi(q); |
524 |
|
|
} else |
525 |
|
|
working->uid = (uid_t)-1; |
526 |
|
|
|
527 |
|
|
q = group; |
528 |
|
|
if (*q) { |
529 |
|
|
if (!(isnumberstr(q))) { |
530 |
|
|
if ((grp = getgrnam(q)) == NULL) |
531 |
|
|
errx(1, "%s:%d: unknown group: %s", |
532 |
|
|
conf, lineno, q); |
533 |
|
|
working->gid = grp->gr_gid; |
534 |
|
|
} else |
535 |
|
|
working->gid = atoi(q); |
536 |
|
|
} else |
537 |
|
|
working->gid = (gid_t)-1; |
538 |
|
|
|
539 |
|
|
q = parse = missing_field(sob(++parse), errline, lineno); |
540 |
|
|
*(parse = son(parse)) = '\0'; |
541 |
|
|
} else { |
542 |
|
|
working->uid = (uid_t)-1; |
543 |
|
|
working->gid = (gid_t)-1; |
544 |
|
|
} |
545 |
|
|
|
546 |
|
|
l = strtol(q, &ep, 8); |
547 |
|
|
if (*ep != '\0' || l < 0 || l > ALLPERMS) |
548 |
|
|
errx(1, "%s:%d: bad permissions: %s", conf, lineno, q); |
549 |
|
|
working->permissions = (mode_t)l; |
550 |
|
|
|
551 |
|
|
q = parse = missing_field(sob(++parse), errline, lineno); |
552 |
|
|
*(parse = son(parse)) = '\0'; |
553 |
|
|
l = strtol(q, &ep, 10); |
554 |
|
|
if (*ep != '\0' || l < 0 || l >= INT_MAX) |
555 |
|
|
errx(1, "%s:%d: bad number: %s", conf, lineno, q); |
556 |
|
|
working->numlogs = (int)l; |
557 |
|
|
|
558 |
|
|
q = parse = missing_field(sob(++parse), errline, lineno); |
559 |
|
|
*(parse = son(parse)) = '\0'; |
560 |
|
|
if (isdigit((unsigned char)*q)) |
561 |
|
|
working->size = atoi(q) * 1024; |
562 |
|
|
else |
563 |
|
|
working->size = -1; |
564 |
|
|
|
565 |
|
|
working->flags = 0; |
566 |
|
|
q = parse = missing_field(sob(++parse), errline, lineno); |
567 |
|
|
*(parse = son(parse)) = '\0'; |
568 |
|
|
l = strtol(q, &ep, 10); |
569 |
|
|
if (l < 0 || l >= INT_MAX) |
570 |
|
|
errx(1, "%s:%d: interval out of range: %s", conf, |
571 |
|
|
lineno, q); |
572 |
|
|
working->hours = (int)l; |
573 |
|
|
switch (*ep) { |
574 |
|
|
case '\0': |
575 |
|
|
break; |
576 |
|
|
case '@': |
577 |
|
|
working->trim_at = parse8601(ep + 1); |
578 |
|
|
if (working->trim_at == (time_t) - 1) |
579 |
|
|
errx(1, "%s:%d: bad time: %s", conf, lineno, q); |
580 |
|
|
working->flags |= CE_TRIMAT; |
581 |
|
|
break; |
582 |
|
|
case '$': |
583 |
|
|
working->trim_at = parseDWM(ep + 1); |
584 |
|
|
if (working->trim_at == (time_t) - 1) |
585 |
|
|
errx(1, "%s:%d: bad time: %s", conf, lineno, q); |
586 |
|
|
working->flags |= CE_TRIMAT; |
587 |
|
|
break; |
588 |
|
|
case '*': |
589 |
|
|
if (q == ep) |
590 |
|
|
break; |
591 |
|
|
/* FALLTHROUGH */ |
592 |
|
|
default: |
593 |
|
|
errx(1, "%s:%d: bad interval/at: %s", conf, lineno, q); |
594 |
|
|
break; |
595 |
|
|
} |
596 |
|
|
|
597 |
|
|
q = sob(++parse); /* Optional field */ |
598 |
|
|
if (*q == 'Z' || *q == 'z' || *q == 'B' || *q == 'b' || |
599 |
|
|
*q == 'M' || *q == 'm') { |
600 |
|
|
*(parse = son(q)) = '\0'; |
601 |
|
|
while (*q) { |
602 |
|
|
switch (*q) { |
603 |
|
|
case 'Z': |
604 |
|
|
case 'z': |
605 |
|
|
working->flags |= CE_COMPACT; |
606 |
|
|
break; |
607 |
|
|
case 'B': |
608 |
|
|
case 'b': |
609 |
|
|
working->flags |= CE_BINARY; |
610 |
|
|
break; |
611 |
|
|
case 'M': |
612 |
|
|
case 'm': |
613 |
|
|
working->flags |= CE_MONITOR; |
614 |
|
|
break; |
615 |
|
|
case 'F': |
616 |
|
|
case 'f': |
617 |
|
|
working->flags |= CE_FOLLOW; |
618 |
|
|
break; |
619 |
|
|
default: |
620 |
|
|
errx(1, "%s:%d: illegal flag: `%c'", |
621 |
|
|
conf, lineno, *q); |
622 |
|
|
break; |
623 |
|
|
} |
624 |
|
|
q++; |
625 |
|
|
} |
626 |
|
|
} else |
627 |
|
|
parse--; /* no flags so undo */ |
628 |
|
|
|
629 |
|
|
working->pidfile = PIDFILE; |
630 |
|
|
working->signal = SIGHUP; |
631 |
|
|
working->runcmd = NULL; |
632 |
|
|
working->whom = NULL; |
633 |
|
|
for (;;) { |
634 |
|
|
q = parse = sob(++parse); /* Optional field */ |
635 |
|
|
if (q == NULL || *q == '\0') |
636 |
|
|
break; |
637 |
|
|
if (*q == '/') { |
638 |
|
|
*(parse = son(parse)) = '\0'; |
639 |
|
|
if (strlen(q) >= PATH_MAX) |
640 |
|
|
errx(1, "%s:%d: pathname too long: %s", |
641 |
|
|
conf, lineno, q); |
642 |
|
|
working->pidfile = strdup(q); |
643 |
|
|
if (working->pidfile == NULL) |
644 |
|
|
err(1, "strdup"); |
645 |
|
|
} else if (*q == '"' && (tmp = strchr(q + 1, '"'))) { |
646 |
|
|
*(parse = tmp) = '\0'; |
647 |
|
|
if (*++q != '\0') { |
648 |
|
|
working->runcmd = strdup(q); |
649 |
|
|
if (working->runcmd == NULL) |
650 |
|
|
err(1, "strdup"); |
651 |
|
|
} |
652 |
|
|
working->pidfile = NULL; |
653 |
|
|
working->signal = -1; |
654 |
|
|
} else if (strncmp(q, "SIG", 3) == 0) { |
655 |
|
|
int i; |
656 |
|
|
|
657 |
|
|
*(parse = son(parse)) = '\0'; |
658 |
|
|
for (i = 1; i < NSIG; i++) { |
659 |
|
|
if (!strcmp(sys_signame[i], q + 3)) { |
660 |
|
|
working->signal = i; |
661 |
|
|
break; |
662 |
|
|
} |
663 |
|
|
} |
664 |
|
|
if (i == NSIG) |
665 |
|
|
errx(1, "%s:%d: unknown signal: %s", |
666 |
|
|
conf, lineno, q); |
667 |
|
|
} else if (working->flags & CE_MONITOR) { |
668 |
|
|
*(parse = son(parse)) = '\0'; |
669 |
|
|
working->whom = strdup(q); |
670 |
|
|
if (working->whom == NULL) |
671 |
|
|
err(1, "strdup"); |
672 |
|
|
} else |
673 |
|
|
errx(1, "%s:%d: unrecognized field: %s", |
674 |
|
|
conf, lineno, q); |
675 |
|
|
} |
676 |
|
|
free(errline); |
677 |
|
|
|
678 |
|
|
if ((working->flags & CE_MONITOR) && working->whom == NULL) |
679 |
|
|
errx(1, "%s:%d: missing monitor notification field", |
680 |
|
|
conf, lineno); |
681 |
|
|
|
682 |
|
|
/* If there is an arcdir, set working->backdir. */ |
683 |
|
|
if (arcdir != NULL && working->logbase != NULL) { |
684 |
|
|
if (*arcdir == '/') { |
685 |
|
|
/* Fully qualified arcdir */ |
686 |
|
|
working->backdir = arcdir; |
687 |
|
|
} else { |
688 |
|
|
/* arcdir is relative to log's parent dir */ |
689 |
|
|
*(working->logbase - 1) = '\0'; |
690 |
|
|
if ((asprintf(&working->backdir, "%s/%s", |
691 |
|
|
working->log, arcdir)) == -1) |
692 |
|
|
err(1, "malloc"); |
693 |
|
|
*(working->logbase - 1) = '/'; |
694 |
|
|
} |
695 |
|
|
/* Ignore arcdir if it doesn't exist. */ |
696 |
|
|
if (stat(working->backdir, &sb) != 0 || |
697 |
|
|
!S_ISDIR(sb.st_mode)) { |
698 |
|
|
if (working->backdir != arcdir) |
699 |
|
|
free(working->backdir); |
700 |
|
|
working->backdir = NULL; |
701 |
|
|
} |
702 |
|
|
} else |
703 |
|
|
working->backdir = NULL; |
704 |
|
|
|
705 |
|
|
/* Make sure we can't oflow PATH_MAX */ |
706 |
|
|
if (working->backdir != NULL) { |
707 |
|
|
if (snprintf(line, sizeof(line), "%s/%s.%d%s", |
708 |
|
|
working->backdir, working->logbase, |
709 |
|
|
working->numlogs, COMPRESS_POSTFIX) >= PATH_MAX) |
710 |
|
|
errx(1, "%s:%d: pathname too long: %s", |
711 |
|
|
conf, lineno, q); |
712 |
|
|
} else { |
713 |
|
|
if (snprintf(line, sizeof(line), "%s.%d%s", |
714 |
|
|
working->log, working->numlogs, COMPRESS_POSTFIX) |
715 |
|
|
>= PATH_MAX) |
716 |
|
|
errx(1, "%s:%d: pathname too long: %s", |
717 |
|
|
conf, lineno, working->log); |
718 |
|
|
} |
719 |
|
|
} |
720 |
|
|
if (working) |
721 |
|
|
working->next = NULL; |
722 |
|
|
(void)fclose(f); |
723 |
|
|
return (first); |
724 |
|
|
} |
725 |
|
|
|
726 |
|
|
char * |
727 |
|
|
missing_field(char *p, char *errline, int lineno) |
728 |
|
|
{ |
729 |
|
|
if (p == NULL || *p == '\0') { |
730 |
|
|
warnx("%s:%d: missing field", conf, lineno); |
731 |
|
|
fputs(errline, stderr); |
732 |
|
|
exit(1); |
733 |
|
|
} |
734 |
|
|
return (p); |
735 |
|
|
} |
736 |
|
|
|
737 |
|
|
void |
738 |
|
|
rotate(struct conf_entry *ent, const char *oldlog) |
739 |
|
|
{ |
740 |
|
|
char file1[PATH_MAX], file2[PATH_MAX], *suffix; |
741 |
|
|
int numdays = ent->numlogs - 1; |
742 |
|
|
int done = 0; |
743 |
|
|
|
744 |
|
|
/* Remove old logs */ |
745 |
|
|
do { |
746 |
|
|
(void)snprintf(file1, sizeof(file1), "%s.%d", oldlog, numdays); |
747 |
|
|
(void)snprintf(file2, sizeof(file2), "%s.%d%s", oldlog, |
748 |
|
|
numdays, COMPRESS_POSTFIX); |
749 |
|
|
if (noaction) { |
750 |
|
|
printf("\trm -f %s %s\n", file1, file2); |
751 |
|
|
done = access(file1, 0) && access(file2, 0); |
752 |
|
|
} else { |
753 |
|
|
done = unlink(file1) && unlink(file2); |
754 |
|
|
} |
755 |
|
|
numdays++; |
756 |
|
|
} while (done == 0); |
757 |
|
|
|
758 |
|
|
/* Move down log files */ |
759 |
|
|
for (numdays = ent->numlogs - 2; numdays >= 0; numdays--) { |
760 |
|
|
/* |
761 |
|
|
* If both the compressed archive and the non-compressed archive |
762 |
|
|
* exist, we decide which to rotate based on the CE_COMPACT flag |
763 |
|
|
*/ |
764 |
|
|
(void)snprintf(file1, sizeof(file1), "%s.%d", oldlog, numdays); |
765 |
|
|
suffix = lstat_log(file1, sizeof(file1), ent->flags); |
766 |
|
|
if (suffix == NULL) |
767 |
|
|
continue; |
768 |
|
|
(void)snprintf(file2, sizeof(file2), "%s.%d%s", oldlog, |
769 |
|
|
numdays + 1, suffix); |
770 |
|
|
|
771 |
|
|
if (noaction) { |
772 |
|
|
printf("\tmv %s %s\n", file1, file2); |
773 |
|
|
printf("\tchmod %o %s\n", ent->permissions, file2); |
774 |
|
|
printf("\tchown %u:%u %s\n", ent->uid, ent->gid, file2); |
775 |
|
|
} else { |
776 |
|
|
if (rename(file1, file2)) |
777 |
|
|
warn("can't mv %s to %s", file1, file2); |
778 |
|
|
if (chmod(file2, ent->permissions)) |
779 |
|
|
warn("can't chmod %s", file2); |
780 |
|
|
if (chown(file2, ent->uid, ent->gid)) |
781 |
|
|
warn("can't chown %s", file2); |
782 |
|
|
} |
783 |
|
|
} |
784 |
|
|
} |
785 |
|
|
|
786 |
|
|
void |
787 |
|
|
dotrim(struct conf_entry *ent) |
788 |
|
|
{ |
789 |
|
|
char file1[PATH_MAX], file2[PATH_MAX], oldlog[PATH_MAX]; |
790 |
|
|
int fd; |
791 |
|
|
|
792 |
|
|
/* Is there a separate backup dir? */ |
793 |
|
|
if (ent->backdir != NULL) |
794 |
|
|
snprintf(oldlog, sizeof(oldlog), "%s/%s", ent->backdir, |
795 |
|
|
ent->logbase); |
796 |
|
|
else |
797 |
|
|
strlcpy(oldlog, ent->log, sizeof(oldlog)); |
798 |
|
|
|
799 |
|
|
if (ent->numlogs > 0) |
800 |
|
|
rotate(ent, oldlog); |
801 |
|
|
if (!noaction && !(ent->flags & CE_BINARY)) |
802 |
|
|
(void)log_trim(ent->log); |
803 |
|
|
|
804 |
|
|
(void)snprintf(file2, sizeof(file2), "%s.XXXXXXXXXX", ent->log); |
805 |
|
|
if (noaction) { |
806 |
|
|
printf("\tmktemp %s\n", file2); |
807 |
|
|
} else { |
808 |
|
|
if ((fd = mkstemp(file2)) < 0) |
809 |
|
|
err(1, "can't start '%s' log", file2); |
810 |
|
|
if (fchmod(fd, ent->permissions)) |
811 |
|
|
err(1, "can't chmod '%s' log file", file2); |
812 |
|
|
if (fchown(fd, ent->uid, ent->gid)) |
813 |
|
|
err(1, "can't chown '%s' log file", file2); |
814 |
|
|
(void)close(fd); |
815 |
|
|
/* Add status message */ |
816 |
|
|
if (!(ent->flags & CE_BINARY) && log_trim(file2)) |
817 |
|
|
err(1, "can't add status message to log '%s'", file2); |
818 |
|
|
} |
819 |
|
|
|
820 |
|
|
if (ent->numlogs == 0) { |
821 |
|
|
if (noaction) |
822 |
|
|
printf("\trm %s\n", ent->log); |
823 |
|
|
else if (unlink(ent->log)) |
824 |
|
|
warn("can't rm %s", ent->log); |
825 |
|
|
} else { |
826 |
|
|
(void)snprintf(file1, sizeof(file1), "%s.0", oldlog); |
827 |
|
|
if (noaction) { |
828 |
|
|
printf("\tmv %s to %s\n", ent->log, file1); |
829 |
|
|
printf("\tchmod %o %s\n", ent->permissions, file1); |
830 |
|
|
printf("\tchown %u:%u %s\n", ent->uid, ent->gid, file1); |
831 |
|
|
} else if (movefile(ent->log, file1, ent->uid, ent->gid, |
832 |
|
|
ent->permissions)) |
833 |
|
|
warn("can't mv %s to %s", ent->log, file1); |
834 |
|
|
} |
835 |
|
|
|
836 |
|
|
/* Now move the new log file into place */ |
837 |
|
|
if (noaction) |
838 |
|
|
printf("\tmv %s to %s\n", file2, ent->log); |
839 |
|
|
else if (rename(file2, ent->log)) |
840 |
|
|
warn("can't mv %s to %s", file2, ent->log); |
841 |
|
|
} |
842 |
|
|
|
843 |
|
|
/* Log the fact that the logs were turned over */ |
844 |
|
|
int |
845 |
|
|
log_trim(char *log) |
846 |
|
|
{ |
847 |
|
|
FILE *f; |
848 |
|
|
|
849 |
|
|
if ((f = fopen(log, "a")) == NULL) |
850 |
|
|
return (-1); |
851 |
|
|
(void)fprintf(f, "%s %s newsyslog[%ld]: logfile turned over\n", |
852 |
|
|
daytime, hostname, (long)getpid()); |
853 |
|
|
if (fclose(f) == EOF) |
854 |
|
|
err(1, "log_trim: fclose"); |
855 |
|
|
return (0); |
856 |
|
|
} |
857 |
|
|
|
858 |
|
|
/* Fork off compress or gzip to compress the old log file */ |
859 |
|
|
void |
860 |
|
|
compress_log(struct conf_entry *ent) |
861 |
|
|
{ |
862 |
|
|
char *base, tmp[PATH_MAX]; |
863 |
|
|
pid_t pid; |
864 |
|
|
|
865 |
|
|
if (ent->backdir != NULL) |
866 |
|
|
snprintf(tmp, sizeof(tmp), "%s/%s.0", ent->backdir, |
867 |
|
|
ent->logbase); |
868 |
|
|
else |
869 |
|
|
snprintf(tmp, sizeof(tmp), "%s.0", ent->log); |
870 |
|
|
|
871 |
|
|
if ((base = strrchr(COMPRESS, '/')) == NULL) |
872 |
|
|
base = COMPRESS; |
873 |
|
|
else |
874 |
|
|
base++; |
875 |
|
|
if (noaction) { |
876 |
|
|
printf("%s %s\n", base, tmp); |
877 |
|
|
return; |
878 |
|
|
} |
879 |
|
|
pid = fork(); |
880 |
|
|
if (pid < 0) { |
881 |
|
|
err(1, "fork"); |
882 |
|
|
} else if (pid == 0) { |
883 |
|
|
(void)execl(COMPRESS, base, "-f", tmp, (char *)NULL); |
884 |
|
|
warn(COMPRESS); |
885 |
|
|
_exit(1); |
886 |
|
|
} |
887 |
|
|
} |
888 |
|
|
|
889 |
|
|
/* Return size in bytes of a file */ |
890 |
|
|
off_t |
891 |
|
|
sizefile(struct stat *sb) |
892 |
|
|
{ |
893 |
|
|
/* For sparse files, return the size based on number of blocks used. */ |
894 |
|
|
if (sb->st_size / DEV_BSIZE > sb->st_blocks) |
895 |
|
|
return (sb->st_blocks * DEV_BSIZE); |
896 |
|
|
else |
897 |
|
|
return (sb->st_size); |
898 |
|
|
} |
899 |
|
|
|
900 |
|
|
/* Return the age (in hours) of old log file (file.0), or -1 if none */ |
901 |
|
|
int |
902 |
|
|
age_old_log(struct conf_entry *ent) |
903 |
|
|
{ |
904 |
|
|
char file[PATH_MAX]; |
905 |
|
|
struct stat sb; |
906 |
|
|
|
907 |
|
|
if (ent->backdir != NULL) |
908 |
|
|
(void)snprintf(file, sizeof(file), "%s/%s.0", ent->backdir, |
909 |
|
|
ent->logbase); |
910 |
|
|
else |
911 |
|
|
(void)snprintf(file, sizeof(file), "%s.0", ent->log); |
912 |
|
|
if (ent->flags & CE_COMPACT) { |
913 |
|
|
if (stat_suffix(file, sizeof(file), COMPRESS_POSTFIX, &sb, |
914 |
|
|
stat) < 0 && stat(file, &sb) < 0) |
915 |
|
|
return (-1); |
916 |
|
|
} else { |
917 |
|
|
if (stat(file, &sb) < 0 && stat_suffix(file, sizeof(file), |
918 |
|
|
COMPRESS_POSTFIX, &sb, stat) < 0) |
919 |
|
|
return (-1); |
920 |
|
|
} |
921 |
|
|
return ((int)(timenow - sb.st_mtime + 1800) / 3600); |
922 |
|
|
} |
923 |
|
|
|
924 |
|
|
/* Skip Over Blanks */ |
925 |
|
|
char * |
926 |
|
|
sob(char *p) |
927 |
|
|
{ |
928 |
|
|
if (p == NULL) |
929 |
|
|
return(p); |
930 |
|
|
while (isspace((unsigned char)*p)) |
931 |
|
|
p++; |
932 |
|
|
return (p); |
933 |
|
|
} |
934 |
|
|
|
935 |
|
|
/* Skip Over Non-Blanks */ |
936 |
|
|
char * |
937 |
|
|
son(char *p) |
938 |
|
|
{ |
939 |
|
|
while (p && *p && !isspace((unsigned char)*p)) |
940 |
|
|
p++; |
941 |
|
|
return (p); |
942 |
|
|
} |
943 |
|
|
|
944 |
|
|
/* Check if string is actually a number */ |
945 |
|
|
int |
946 |
|
|
isnumberstr(char *string) |
947 |
|
|
{ |
948 |
|
|
while (*string) { |
949 |
|
|
if (!isdigit((unsigned char)*string++)) |
950 |
|
|
return (0); |
951 |
|
|
} |
952 |
|
|
return (1); |
953 |
|
|
} |
954 |
|
|
|
955 |
|
|
int |
956 |
|
|
domonitor(struct conf_entry *ent) |
957 |
|
|
{ |
958 |
|
|
char fname[PATH_MAX], *flog, *p, *rb = NULL; |
959 |
|
|
struct stat sb, tsb; |
960 |
|
|
off_t osize; |
961 |
|
|
FILE *fp; |
962 |
|
|
int rd; |
963 |
|
|
|
964 |
|
|
if (stat(ent->log, &sb) < 0) |
965 |
|
|
return (0); |
966 |
|
|
|
967 |
|
|
if (noaction) { |
968 |
|
|
if (!verbose) |
969 |
|
|
printf("%s: monitored\n", ent->log); |
970 |
|
|
return (1); |
971 |
|
|
} |
972 |
|
|
|
973 |
|
|
flog = strdup(ent->log); |
974 |
|
|
if (flog == NULL) |
975 |
|
|
err(1, "strdup"); |
976 |
|
|
|
977 |
|
|
for (p = flog; *p != '\0'; p++) { |
978 |
|
|
if (*p == '/') |
979 |
|
|
*p = '_'; |
980 |
|
|
} |
981 |
|
|
snprintf(fname, sizeof(fname), "%s/newsyslog.%s.size", |
982 |
|
|
STATS_DIR, flog); |
983 |
|
|
|
984 |
|
|
/* ..if it doesn't exist, simply record the current size. */ |
985 |
|
|
if ((sb.st_size == 0) || stat(fname, &tsb) < 0) |
986 |
|
|
goto update; |
987 |
|
|
|
988 |
|
|
fp = fopen(fname, "r"); |
989 |
|
|
if (fp == NULL) { |
990 |
|
|
warn("%s", fname); |
991 |
|
|
goto cleanup; |
992 |
|
|
} |
993 |
|
|
if (fscanf(fp, "%lld\n", &osize) != 1) { |
994 |
|
|
fclose(fp); |
995 |
|
|
goto update; |
996 |
|
|
} |
997 |
|
|
|
998 |
|
|
fclose(fp); |
999 |
|
|
|
1000 |
|
|
/* If the file is smaller, mark the entire thing as changed. */ |
1001 |
|
|
if (sb.st_size < osize) |
1002 |
|
|
osize = 0; |
1003 |
|
|
|
1004 |
|
|
/* Now see if current size is larger. */ |
1005 |
|
|
if (sb.st_size > osize) { |
1006 |
|
|
rb = malloc(sb.st_size - osize); |
1007 |
|
|
if (rb == NULL) |
1008 |
|
|
err(1, "malloc"); |
1009 |
|
|
|
1010 |
|
|
/* Open logfile, seek. */ |
1011 |
|
|
fp = fopen(ent->log, "r"); |
1012 |
|
|
if (fp == NULL) { |
1013 |
|
|
warn("%s", ent->log); |
1014 |
|
|
goto cleanup; |
1015 |
|
|
} |
1016 |
|
|
fseek(fp, osize, SEEK_SET); |
1017 |
|
|
rd = fread(rb, 1, sb.st_size - osize, fp); |
1018 |
|
|
if (rd < 1) { |
1019 |
|
|
warn("fread"); |
1020 |
|
|
fclose(fp); |
1021 |
|
|
goto cleanup; |
1022 |
|
|
} |
1023 |
|
|
|
1024 |
|
|
/* Send message. */ |
1025 |
|
|
fclose(fp); |
1026 |
|
|
|
1027 |
|
|
fp = openmail(); |
1028 |
|
|
if (fp == NULL) { |
1029 |
|
|
warn("openmail"); |
1030 |
|
|
goto cleanup; |
1031 |
|
|
} |
1032 |
|
|
fprintf(fp, "Auto-Submitted: auto-generated\n"); |
1033 |
|
|
fprintf(fp, "To: %s\nSubject: LOGFILE NOTIFICATION: %s\n\n\n", |
1034 |
|
|
ent->whom, ent->log); |
1035 |
|
|
fwrite(rb, 1, rd, fp); |
1036 |
|
|
fputs("\n\n", fp); |
1037 |
|
|
|
1038 |
|
|
pclose(fp); |
1039 |
|
|
} |
1040 |
|
|
update: |
1041 |
|
|
/* Reopen for writing and update file. */ |
1042 |
|
|
fp = fopen(fname, "w"); |
1043 |
|
|
if (fp == NULL) { |
1044 |
|
|
warn("%s", fname); |
1045 |
|
|
goto cleanup; |
1046 |
|
|
} |
1047 |
|
|
fprintf(fp, "%lld\n", (long long)sb.st_size); |
1048 |
|
|
fclose(fp); |
1049 |
|
|
|
1050 |
|
|
cleanup: |
1051 |
|
|
free(flog); |
1052 |
|
|
free(rb); |
1053 |
|
|
return (1); |
1054 |
|
|
} |
1055 |
|
|
|
1056 |
|
|
FILE * |
1057 |
|
|
openmail(void) |
1058 |
|
|
{ |
1059 |
|
|
char *cmdbuf = NULL; |
1060 |
|
|
FILE *ret; |
1061 |
|
|
|
1062 |
|
|
if (asprintf(&cmdbuf, "%s -t", SENDMAIL) != -1) { |
1063 |
|
|
ret = popen(cmdbuf, "w"); |
1064 |
|
|
free(cmdbuf); |
1065 |
|
|
return (ret); |
1066 |
|
|
} |
1067 |
|
|
return (NULL); |
1068 |
|
|
} |
1069 |
|
|
|
1070 |
|
|
/* ARGSUSED */ |
1071 |
|
|
void |
1072 |
|
|
child_killer(int signo) |
1073 |
|
|
{ |
1074 |
|
|
int save_errno = errno; |
1075 |
|
|
int status; |
1076 |
|
|
|
1077 |
|
|
while (waitpid(-1, &status, WNOHANG) > 0) |
1078 |
|
|
; |
1079 |
|
|
errno = save_errno; |
1080 |
|
|
} |
1081 |
|
|
|
1082 |
|
|
int |
1083 |
|
|
stat_suffix(char *file, size_t size, char *suffix, struct stat *sp, |
1084 |
|
|
int (*func)(const char *, struct stat *)) |
1085 |
|
|
{ |
1086 |
|
|
size_t n; |
1087 |
|
|
|
1088 |
|
|
n = strlcat(file, suffix, size); |
1089 |
|
|
if (n < size && func(file, sp) == 0) |
1090 |
|
|
return (0); |
1091 |
|
|
file[n - strlen(suffix)] = '\0'; |
1092 |
|
|
return (-1); |
1093 |
|
|
} |
1094 |
|
|
|
1095 |
|
|
/* |
1096 |
|
|
* lstat() a log, possibly appending a suffix; order is based on flags. |
1097 |
|
|
* Returns the suffix appended (may be empty string) or NULL if no file. |
1098 |
|
|
*/ |
1099 |
|
|
char * |
1100 |
|
|
lstat_log(char *file, size_t size, int flags) |
1101 |
|
|
{ |
1102 |
|
|
struct stat sb; |
1103 |
|
|
|
1104 |
|
|
if (flags & CE_COMPACT) { |
1105 |
|
|
if (stat_suffix(file, size, COMPRESS_POSTFIX, &sb, lstat) == 0) |
1106 |
|
|
return (COMPRESS_POSTFIX); |
1107 |
|
|
if (lstat(file, &sb) == 0) |
1108 |
|
|
return (""); |
1109 |
|
|
} else { |
1110 |
|
|
if (lstat(file, &sb) == 0) |
1111 |
|
|
return (""); |
1112 |
|
|
if (stat_suffix(file, size, COMPRESS_POSTFIX, &sb, lstat) == 0) |
1113 |
|
|
return (COMPRESS_POSTFIX); |
1114 |
|
|
|
1115 |
|
|
} |
1116 |
|
|
return (NULL); |
1117 |
|
|
} |
1118 |
|
|
|
1119 |
|
|
/* |
1120 |
|
|
* Parse a limited subset of ISO 8601. The specific format is as follows: |
1121 |
|
|
* |
1122 |
|
|
* [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) |
1123 |
|
|
* |
1124 |
|
|
* We don't accept a timezone specification; missing fields (including timezone) |
1125 |
|
|
* are defaulted to the current date but time zero. |
1126 |
|
|
*/ |
1127 |
|
|
time_t |
1128 |
|
|
parse8601(char *s) |
1129 |
|
|
{ |
1130 |
|
|
struct tm tm, *tmp; |
1131 |
|
|
char *t; |
1132 |
|
|
long l; |
1133 |
|
|
|
1134 |
|
|
tmp = localtime(&timenow); |
1135 |
|
|
tm = *tmp; |
1136 |
|
|
|
1137 |
|
|
tm.tm_hour = tm.tm_min = tm.tm_sec = 0; |
1138 |
|
|
|
1139 |
|
|
l = strtol(s, &t, 10); |
1140 |
|
|
if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T')) |
1141 |
|
|
return (-1); |
1142 |
|
|
|
1143 |
|
|
/* |
1144 |
|
|
* Now t points either to the end of the string (if no time was |
1145 |
|
|
* provided) or to the letter `T' which separates date and time in |
1146 |
|
|
* ISO 8601. The pointer arithmetic is the same for either case. |
1147 |
|
|
*/ |
1148 |
|
|
switch (t - s) { |
1149 |
|
|
case 8: |
1150 |
|
|
tm.tm_year = ((l / 1000000) - 19) * 100; |
1151 |
|
|
l = l % 1000000; |
1152 |
|
|
case 6: |
1153 |
|
|
tm.tm_year -= tm.tm_year % 100; |
1154 |
|
|
tm.tm_year += l / 10000; |
1155 |
|
|
l = l % 10000; |
1156 |
|
|
case 4: |
1157 |
|
|
tm.tm_mon = (l / 100) - 1; |
1158 |
|
|
l = l % 100; |
1159 |
|
|
case 2: |
1160 |
|
|
tm.tm_mday = l; |
1161 |
|
|
case 0: |
1162 |
|
|
break; |
1163 |
|
|
default: |
1164 |
|
|
return (-1); |
1165 |
|
|
} |
1166 |
|
|
|
1167 |
|
|
/* sanity check */ |
1168 |
|
|
if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 || |
1169 |
|
|
tm.tm_mday < 1 || tm.tm_mday > 31) |
1170 |
|
|
return (-1); |
1171 |
|
|
|
1172 |
|
|
if (*t != '\0') { |
1173 |
|
|
s = ++t; |
1174 |
|
|
l = strtol(s, &t, 10); |
1175 |
|
|
if (l < 0 || l >= INT_MAX || |
1176 |
|
|
(*t != '\0' && !isspace((unsigned char)*t))) |
1177 |
|
|
return (-1); |
1178 |
|
|
|
1179 |
|
|
switch (t - s) { |
1180 |
|
|
case 6: |
1181 |
|
|
tm.tm_sec = l % 100; |
1182 |
|
|
l /= 100; |
1183 |
|
|
case 4: |
1184 |
|
|
tm.tm_min = l % 100; |
1185 |
|
|
l /= 100; |
1186 |
|
|
case 2: |
1187 |
|
|
tm.tm_hour = l; |
1188 |
|
|
case 0: |
1189 |
|
|
break; |
1190 |
|
|
default: |
1191 |
|
|
return (-1); |
1192 |
|
|
} |
1193 |
|
|
|
1194 |
|
|
/* sanity check */ |
1195 |
|
|
if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 || |
1196 |
|
|
tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) |
1197 |
|
|
return (-1); |
1198 |
|
|
} |
1199 |
|
|
return (mktime(&tm)); |
1200 |
|
|
} |
1201 |
|
|
|
1202 |
|
|
/*- |
1203 |
|
|
* Parse a cyclic time specification, the format is as follows: |
1204 |
|
|
* |
1205 |
|
|
* [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] |
1206 |
|
|
* |
1207 |
|
|
* to rotate a logfile cyclic at |
1208 |
|
|
* |
1209 |
|
|
* - every day (D) within a specific hour (hh) (hh = 0...23) |
1210 |
|
|
* - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) |
1211 |
|
|
* - once a month (M) at a specific day (d) (d = 1..31,l|L) |
1212 |
|
|
* |
1213 |
|
|
* We don't accept a timezone specification; missing fields |
1214 |
|
|
* are defaulted to the current date but time zero. |
1215 |
|
|
*/ |
1216 |
|
|
time_t |
1217 |
|
|
parseDWM(char *s) |
1218 |
|
|
{ |
1219 |
|
|
static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; |
1220 |
|
|
int WMseen = 0, Dseen = 0, nd; |
1221 |
|
|
struct tm tm, *tmp; |
1222 |
|
|
char *t; |
1223 |
|
|
long l; |
1224 |
|
|
|
1225 |
|
|
tmp = localtime(&timenow); |
1226 |
|
|
tm = *tmp; |
1227 |
|
|
|
1228 |
|
|
/* set no. of days per month */ |
1229 |
|
|
|
1230 |
|
|
nd = mtab[tm.tm_mon]; |
1231 |
|
|
|
1232 |
|
|
if (tm.tm_mon == 1) { |
1233 |
|
|
if (((tm.tm_year + 1900) % 4 == 0) && |
1234 |
|
|
((tm.tm_year + 1900) % 100 != 0) && |
1235 |
|
|
((tm.tm_year + 1900) % 400 == 0)) { |
1236 |
|
|
nd++; /* leap year, 29 days in february */ |
1237 |
|
|
} |
1238 |
|
|
} |
1239 |
|
|
tm.tm_hour = tm.tm_min = tm.tm_sec = 0; |
1240 |
|
|
|
1241 |
|
|
for (;;) { |
1242 |
|
|
switch (*s) { |
1243 |
|
|
case 'D': |
1244 |
|
|
if (Dseen) |
1245 |
|
|
return (-1); |
1246 |
|
|
Dseen++; |
1247 |
|
|
s++; |
1248 |
|
|
l = strtol(s, &t, 10); |
1249 |
|
|
if (l < 0 || l > 23) |
1250 |
|
|
return (-1); |
1251 |
|
|
tm.tm_hour = l; |
1252 |
|
|
break; |
1253 |
|
|
|
1254 |
|
|
case 'W': |
1255 |
|
|
if (WMseen) |
1256 |
|
|
return (-1); |
1257 |
|
|
WMseen++; |
1258 |
|
|
s++; |
1259 |
|
|
l = strtol(s, &t, 10); |
1260 |
|
|
if (l < 0 || l > 6) |
1261 |
|
|
return (-1); |
1262 |
|
|
if (l != tm.tm_wday) { |
1263 |
|
|
int save; |
1264 |
|
|
|
1265 |
|
|
if (l < tm.tm_wday) { |
1266 |
|
|
save = 6 - tm.tm_wday; |
1267 |
|
|
save += (l + 1); |
1268 |
|
|
} else { |
1269 |
|
|
save = l - tm.tm_wday; |
1270 |
|
|
} |
1271 |
|
|
|
1272 |
|
|
tm.tm_mday += save; |
1273 |
|
|
|
1274 |
|
|
if (tm.tm_mday > nd) { |
1275 |
|
|
tm.tm_mon++; |
1276 |
|
|
tm.tm_mday = tm.tm_mday - nd; |
1277 |
|
|
} |
1278 |
|
|
} |
1279 |
|
|
break; |
1280 |
|
|
|
1281 |
|
|
case 'M': |
1282 |
|
|
if (WMseen) |
1283 |
|
|
return (-1); |
1284 |
|
|
WMseen++; |
1285 |
|
|
s++; |
1286 |
|
|
if (tolower((unsigned char)*s) == 'l') { |
1287 |
|
|
tm.tm_mday = nd; |
1288 |
|
|
s++; |
1289 |
|
|
t = s; |
1290 |
|
|
} else { |
1291 |
|
|
l = strtol(s, &t, 10); |
1292 |
|
|
if (l < 1 || l > 31) |
1293 |
|
|
return (-1); |
1294 |
|
|
|
1295 |
|
|
if (l > nd) |
1296 |
|
|
return (-1); |
1297 |
|
|
if (l < tm.tm_mday) |
1298 |
|
|
tm.tm_mon++; |
1299 |
|
|
tm.tm_mday = l; |
1300 |
|
|
} |
1301 |
|
|
break; |
1302 |
|
|
|
1303 |
|
|
default: |
1304 |
|
|
return (-1); |
1305 |
|
|
break; |
1306 |
|
|
} |
1307 |
|
|
|
1308 |
|
|
if (*t == '\0' || isspace((unsigned char)*t)) |
1309 |
|
|
break; |
1310 |
|
|
else |
1311 |
|
|
s = t; |
1312 |
|
|
} |
1313 |
|
|
return (mktime(&tm)); |
1314 |
|
|
} |
1315 |
|
|
|
1316 |
|
|
/* |
1317 |
|
|
* Move a file using rename(2) if possible and copying if not. |
1318 |
|
|
*/ |
1319 |
|
|
int |
1320 |
|
|
movefile(char *from, char *to, uid_t owner_uid, gid_t group_gid, mode_t perm) |
1321 |
|
|
{ |
1322 |
|
|
FILE *src, *dst; |
1323 |
|
|
int i; |
1324 |
|
|
|
1325 |
|
|
/* try rename(2) first */ |
1326 |
|
|
if (rename(from, to) == 0) { |
1327 |
|
|
if (chmod(to, perm)) |
1328 |
|
|
warn("can't chmod %s", to); |
1329 |
|
|
if (chown(to, owner_uid, group_gid)) |
1330 |
|
|
warn("can't chown %s", to); |
1331 |
|
|
return (0); |
1332 |
|
|
} else if (errno != EXDEV) |
1333 |
|
|
return (-1); |
1334 |
|
|
|
1335 |
|
|
/* different filesystem, have to copy the file */ |
1336 |
|
|
if ((src = fopen(from, "r")) == NULL) |
1337 |
|
|
err(1, "can't fopen %s for reading", from); |
1338 |
|
|
if ((dst = fopen(to, "w")) == NULL) |
1339 |
|
|
err(1, "can't fopen %s for writing", to); |
1340 |
|
|
if (fchmod(fileno(dst), perm)) |
1341 |
|
|
err(1, "can't fchmod %s", to); |
1342 |
|
|
if (fchown(fileno(dst), owner_uid, group_gid)) |
1343 |
|
|
err(1, "can't fchown %s", to); |
1344 |
|
|
|
1345 |
|
|
while ((i = getc(src)) != EOF) { |
1346 |
|
|
if ((putc(i, dst)) == EOF) |
1347 |
|
|
err(1, "error writing to %s", to); |
1348 |
|
|
} |
1349 |
|
|
|
1350 |
|
|
if (ferror(src)) |
1351 |
|
|
err(1, "error reading from %s", from); |
1352 |
|
|
if ((fclose(src)) != 0) |
1353 |
|
|
err(1, "can't fclose %s", from); |
1354 |
|
|
if ((fclose(dst)) != 0) |
1355 |
|
|
err(1, "can't fclose %s", to); |
1356 |
|
|
if ((unlink(from)) != 0) |
1357 |
|
|
err(1, "can't unlink %s", from); |
1358 |
|
|
|
1359 |
|
|
return (0); |
1360 |
|
|
} |