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