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