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