1 /* $NetBSD: newsyslog.c,v 1.57 2009/02/17 02:56:43 dogcow Exp $ */ 2 3 /* 4 * Copyright (c) 1999, 2000 Andrew Doran <ad@NetBSD.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 28 */ 29 30 /* 31 * This file contains changes from the Open Software Foundation. 32 */ 33 34 /* 35 * Copyright 1988, 1989 by the Massachusetts Institute of Technology 36 * 37 * Permission to use, copy, modify, and distribute this software 38 * and its documentation for any purpose and without fee is 39 * hereby granted, provided that the above copyright notice 40 * appear in all copies and that both that copyright notice and 41 * this permission notice appear in supporting documentation, 42 * and that the names of M.I.T. and the M.I.T. S.I.P.B. not be 43 * used in advertising or publicity pertaining to distribution 44 * of the software without specific, written prior permission. 45 * M.I.T. and the M.I.T. S.I.P.B. make no representations about 46 * the suitability of this software for any purpose. It is 47 * provided "as is" without express or implied warranty. 48 * 49 */ 50 51 /* 52 * newsyslog(8) - a program to roll over log files provided that specified 53 * critera are met, optionally preserving a number of historical log files. 54 */ 55 56 #include <sys/cdefs.h> 57 #ifndef lint 58 __RCSID("$NetBSD: newsyslog.c,v 1.57 2009/02/17 02:56:43 dogcow Exp $"); 59 #endif /* not lint */ 60 61 #include <sys/types.h> 62 #include <sys/time.h> 63 #include <sys/stat.h> 64 #include <sys/param.h> 65 #include <sys/wait.h> 66 67 #include <ctype.h> 68 #include <fcntl.h> 69 #include <grp.h> 70 #include <pwd.h> 71 #include <signal.h> 72 #include <stdio.h> 73 #include <stdlib.h> 74 #include <stdarg.h> 75 #include <string.h> 76 #include <time.h> 77 #include <unistd.h> 78 #include <errno.h> 79 #include <err.h> 80 #include <paths.h> 81 82 #define PRHDRINFO(x) \ 83 (/*LINTED*/(void)(verbose ? printf x : 0)) 84 #define PRINFO(x) \ 85 (/*LINTED*/(void)(verbose ? printf(" ") + printf x : 0)) 86 87 #ifndef __arraycount 88 #define __arraycount(a) (sizeof(a) / sizeof(a[0])) 89 #endif 90 91 #define CE_BINARY 0x02 /* Logfile is a binary file/non-syslog */ 92 #define CE_NOSIGNAL 0x04 /* Don't send a signal when trimmed */ 93 #define CE_CREATE 0x08 /* Create log file if none exists */ 94 #define CE_PLAIN0 0x10 /* Do not compress zero'th history file */ 95 #define CE_SYSLPROTOCOL 0x20 /* log in syslog-protocol format, 96 not configurable but detected at runtime */ 97 98 struct conf_entry { 99 uid_t uid; /* Owner of log */ 100 gid_t gid; /* Group of log */ 101 mode_t mode; /* File permissions */ 102 int numhist; /* Number of historical logs to keep */ 103 size_t maxsize; /* Maximum log size */ 104 int maxage; /* Hours between log trimming */ 105 time_t trimat; /* Specific trim time */ 106 int flags; /* Flags (CE_*) */ 107 int signum; /* Signal to send */ 108 char pidfile[MAXPATHLEN]; /* File containing PID to signal */ 109 char logfile[MAXPATHLEN]; /* Path to log file */ 110 }; 111 112 struct compressor { 113 const char *path; 114 const char *args; 115 const char *suffix; 116 const char *flag; /* newsyslog.conf flag */ 117 }; 118 119 static struct compressor compress[] = 120 { 121 {NULL, "", "", ""}, /* 0th compressor is "no compression" */ 122 {"/usr/bin/gzip", "-f", ".gz", "Z"}, 123 {"/usr/bin/bzip2", "-9f", ".bz2", "J"}, 124 }; 125 126 #define _PATH_NEWSYSLOGCONF "/etc/newsyslog.conf" 127 #define _PATH_SYSLOGDPID _PATH_VARRUN"syslogd.pid" 128 129 static int verbose; /* Be verbose */ 130 static int noaction; /* Take no action */ 131 static int nosignal; /* Do not send signals */ 132 static char hostname[MAXHOSTNAMELEN + 1]; /* Hostname, with domain */ 133 static uid_t myeuid; /* EUID we are running with */ 134 static int ziptype; /* compression type, if any */ 135 136 static int getsig(const char *); 137 static int isnumber(const char *); 138 static int parse_cfgline(struct conf_entry *, FILE *, size_t *); 139 static time_t parse_iso8601(char *); 140 static time_t parse_dwm(char *); 141 static int parse_userspec(const char *, struct passwd **, struct group **); 142 static pid_t readpidfile(const char *); 143 static void usage(void) __dead; 144 145 static void log_compress(struct conf_entry *, const char *); 146 static void log_create(struct conf_entry *); 147 static void log_examine(struct conf_entry *, int); 148 static void log_trim(struct conf_entry *); 149 static void log_trimmed(struct conf_entry *); 150 static void log_get_format(struct conf_entry *); 151 152 /* 153 * Program entry point. 154 */ 155 int 156 main(int argc, char **argv) 157 { 158 struct conf_entry log; 159 FILE *fd; 160 const char *cfile; 161 int c, needroot, i, force; 162 size_t lineno; 163 164 force = 0; 165 needroot = 1; 166 ziptype = 0; 167 cfile = _PATH_NEWSYSLOGCONF; 168 169 (void)gethostname(hostname, sizeof(hostname)); 170 hostname[sizeof(hostname) - 1] = '\0'; 171 172 /* Parse command line options. */ 173 while ((c = getopt(argc, argv, "f:nrsvF")) != -1) { 174 switch (c) { 175 case 'f': 176 cfile = optarg; 177 break; 178 case 'n': 179 noaction = 1; 180 verbose = 1; 181 break; 182 case 'r': 183 needroot = 0; 184 break; 185 case 's': 186 nosignal = 1; 187 break; 188 case 'v': 189 verbose = 1; 190 break; 191 case 'F': 192 force = 1; 193 break; 194 default: 195 usage(); 196 /* NOTREACHED */ 197 } 198 } 199 200 myeuid = geteuid(); 201 if (needroot && myeuid != 0) 202 errx(EXIT_FAILURE, "must be run as root"); 203 204 argc -= optind; 205 argv += optind; 206 207 if (strcmp(cfile, "-") == 0) 208 fd = stdin; 209 else if ((fd = fopen(cfile, "rt")) == NULL) 210 err(EXIT_FAILURE, "%s", cfile); 211 212 for (lineno = 0; !parse_cfgline(&log, fd, &lineno);) { 213 /* 214 * If specific log files were specified, touch only 215 * those. 216 */ 217 if (argc != 0) { 218 for (i = 0; i < argc; i++) 219 if (strcmp(log.logfile, argv[i]) == 0) 220 break; 221 if (i == argc) 222 continue; 223 } 224 log_examine(&log, force); 225 } 226 227 if (fd != stdin) 228 (void)fclose(fd); 229 230 exit(EXIT_SUCCESS); 231 /* NOTREACHED */ 232 } 233 234 /* 235 * Parse a single line from the configuration file. 236 */ 237 static int 238 parse_cfgline(struct conf_entry *log, FILE *fd, size_t *_lineno) 239 { 240 char *line, *q, **ap, *argv[10]; 241 struct passwd *pw; 242 struct group *gr; 243 int nf, lineno, i, rv; 244 245 rv = -1; 246 line = NULL; 247 248 /* Place the white-space separated fields into an array. */ 249 do { 250 if (line != NULL) 251 free(line); 252 if ((line = fparseln(fd, NULL, _lineno, NULL, 0)) == NULL) 253 return (rv); 254 lineno = (int)*_lineno; 255 256 for (ap = argv, nf = 0; (*ap = strsep(&line, " \t")) != NULL;) 257 if (**ap != '\0') { 258 if (++nf == sizeof(argv) / sizeof(argv[0])) { 259 warnx("config line %d: " 260 "too many fields", lineno); 261 goto bad; 262 } 263 ap++; 264 } 265 } while (nf == 0); 266 267 if (nf < 6) 268 errx(EXIT_FAILURE, "config line %d: too few fields", lineno); 269 270 (void)memset(log, 0, sizeof(*log)); 271 272 /* logfile_name */ 273 ap = argv; 274 (void)strlcpy(log->logfile, *ap++, sizeof(log->logfile)); 275 if (log->logfile[0] != '/') 276 errx(EXIT_FAILURE, 277 "config line %d: logfile must have a full path", lineno); 278 279 /* owner:group */ 280 if (strchr(*ap, ':') != NULL || strchr(*ap, '.') != NULL) { 281 if (parse_userspec(*ap++, &pw, &gr)) { 282 warnx("config line %d: unknown user/group", lineno); 283 goto bad; 284 } 285 286 /* 287 * We may only change the file's owner as non-root. 288 */ 289 if (myeuid != 0) { 290 if (pw->pw_uid != myeuid) 291 errx(EXIT_FAILURE, "config line %d: user:group " 292 "as non-root must match current user", 293 lineno); 294 log->uid = (uid_t)-1; 295 } else 296 log->uid = pw->pw_uid; 297 log->gid = gr->gr_gid; 298 if (nf < 7) 299 errx(EXIT_FAILURE, "config line %d: too few fields", 300 lineno); 301 } else if (myeuid != 0) { 302 log->uid = (uid_t)-1; 303 log->gid = getegid(); 304 } 305 306 /* mode */ 307 if (sscanf(*ap++, "%o", &i) != 1) { 308 warnx("config line %d: bad permissions", lineno); 309 goto bad; 310 } 311 log->mode = (mode_t)i; 312 313 /* count */ 314 if (sscanf(*ap++, "%d", &log->numhist) != 1) { 315 warnx("config line %d: bad log count", lineno); 316 goto bad; 317 } 318 319 /* size */ 320 if (**ap == '*') 321 log->maxsize = (size_t)-1; 322 else { 323 log->maxsize = (int)strtol(*ap, &q, 0); 324 if (*q != '\0') { 325 warnx("config line %d: bad log size", lineno); 326 goto bad; 327 } 328 } 329 ap++; 330 331 /* when */ 332 log->maxage = -1; 333 log->trimat = (time_t)-1; 334 q = *ap++; 335 336 if (strcmp(q, "*") != 0) { 337 if (isdigit((unsigned char)*q)) 338 log->maxage = (int)strtol(q, &q, 10); 339 340 /* 341 * One class of periodic interval specification can follow a 342 * maximum age specification. Handle it. 343 */ 344 if (*q == '@') { 345 log->trimat = parse_iso8601(q + 1); 346 if (log->trimat == (time_t)-1) { 347 warnx("config line %d: bad trim time", lineno); 348 goto bad; 349 } 350 } else if (*q == '$') { 351 if ((log->trimat = parse_dwm(q + 1)) == (time_t)-1) { 352 warnx("config line %d: bad trim time", lineno); 353 goto bad; 354 } 355 } else if (log->maxage == -1) { 356 warnx("config line %d: bad log age", lineno); 357 goto bad; 358 } 359 } 360 361 /* flags */ 362 log->flags = (nosignal ? CE_NOSIGNAL : 0); 363 364 for (q = *ap++; q != NULL && *q != '\0'; q++) { 365 char qq = toupper((unsigned char)*q); 366 switch (qq) { 367 case 'B': 368 log->flags |= CE_BINARY; 369 break; 370 case 'C': 371 log->flags |= CE_CREATE; 372 break; 373 case 'N': 374 log->flags |= CE_NOSIGNAL; 375 break; 376 case 'P': 377 log->flags |= CE_PLAIN0; 378 break; 379 case 'J': case 'Z': 380 for (ziptype = __arraycount(compress); --ziptype; ) { 381 if (*compress[ziptype].flag == qq) 382 break; 383 } 384 break; 385 case '-': 386 break; 387 default: 388 warnx("config line %d: bad flags", lineno); 389 goto bad; 390 } 391 } 392 393 /* path_to_pidfile */ 394 if (*ap != NULL && **ap == '/') 395 (void)strlcpy(log->pidfile, *ap++, sizeof(log->pidfile)); 396 else 397 log->pidfile[0] = '\0'; 398 399 /* sigtype */ 400 if (*ap != NULL) { 401 if ((log->signum = getsig(*ap++)) < 0) { 402 warnx("config line %d: bad signal type", lineno); 403 goto bad; 404 } 405 } else 406 log->signum = SIGHUP; 407 408 rv = 0; 409 410 bad: 411 free(line); 412 return (rv); 413 } 414 415 /* 416 * Examine a log file. If the trim conditions are met, call log_trim() to 417 * trim the log file. 418 */ 419 static void 420 log_examine(struct conf_entry *log, int force) 421 { 422 struct stat sb; 423 size_t size; 424 int age, trim; 425 unsigned int j; 426 char tmp[MAXPATHLEN]; 427 const char *reason; 428 time_t now; 429 430 now = time(NULL); 431 432 PRHDRINFO(("\n%s <%d%s>: ", log->logfile, log->numhist, 433 compress[ziptype].flag)); 434 435 /* 436 * stat() the logfile. If it doesn't exist and the `c' flag has 437 * been specified, create it. If it doesn't exist and the `c' flag 438 * hasn't been specified, give up. 439 */ 440 if (stat(log->logfile, &sb) < 0) { 441 if (errno == ENOENT && (log->flags & CE_CREATE) != 0) { 442 PRHDRINFO(("creating; ")); 443 if (!noaction) 444 log_create(log); 445 else { 446 PRHDRINFO(("can't proceed with `-n'\n")); 447 return; 448 } 449 if (stat(log->logfile, &sb)) 450 err(EXIT_FAILURE, "%s", log->logfile); 451 } else if (errno == ENOENT) { 452 PRHDRINFO(("does not exist --> skip log\n")); 453 return; 454 } else if (errno != 0) 455 err(EXIT_FAILURE, "%s", log->logfile); 456 } 457 458 if (!S_ISREG(sb.st_mode)) { 459 PRHDRINFO(("not a regular file --> skip log\n")); 460 return; 461 } 462 463 /* Size of the log file in kB. */ 464 size = ((size_t)sb.st_blocks * S_BLKSIZE) >> 10; 465 466 /* 467 * Get the age (expressed in hours) of the current log file with 468 * respect to the newest historical log file. 469 */ 470 age = -1; 471 for (j = 0; j < __arraycount(compress); j++) { 472 (void)strlcpy(tmp, log->logfile, sizeof(tmp)); 473 (void)strlcat(tmp, ".0", sizeof(tmp)); 474 (void)strlcat(tmp, compress[j].suffix, sizeof(tmp)); 475 if (!stat(tmp, &sb)) { 476 age = (int)(now - sb.st_mtime + 1800) / 3600; 477 break; 478 } 479 } 480 481 /* 482 * Examine the set of given trim conditions and if any one is met, 483 * trim the log. 484 * 485 * Note: if `maxage' or `trimat' is used as a trim condition, we 486 * need at least one historical log file to determine the `age' of 487 * the active log file. WRT `trimat', we will trim up to one hour 488 * after the specific trim time has passed - we need to know if 489 * we've trimmed to meet that condition with a previous invocation 490 * of newsyslog(8). 491 */ 492 if (log->maxage >= 0 && (age >= log->maxage || age < 0)) { 493 trim = 1; 494 reason = "log age > interval"; 495 } else if (size >= log->maxsize) { 496 trim = 1; 497 reason = "log size > size"; 498 } else if (log->trimat != (time_t)-1 && now >= log->trimat && 499 (age == -1 || age > 1) && 500 difftime(now, log->trimat) < 60 * 60) { 501 trim = 1; 502 reason = "specific trim time"; 503 } else { 504 trim = force; 505 reason = "trim forced"; 506 } 507 508 if (trim) { 509 PRHDRINFO(("--> trim log (%s)\n", reason)); 510 log_trim(log); 511 } else 512 PRHDRINFO(("--> skip log (trim conditions not met)\n")); 513 } 514 515 /* 516 * Trim the specified log file. 517 */ 518 static void 519 log_trim(struct conf_entry *log) 520 { 521 char file1[MAXPATHLEN], file2[MAXPATHLEN]; 522 int i, j, k; 523 struct stat st; 524 pid_t pid; 525 526 if (log->numhist != 0) { 527 /* Remove oldest historical log. */ 528 for (j = 0; j < (int)__arraycount(compress); j++) { 529 (void)snprintf(file1, sizeof(file1), "%s.%d", 530 log->logfile, log->numhist - 1); 531 (void)strlcat(file1, compress[j].suffix, 532 sizeof(file1)); 533 PRINFO(("rm -f %s\n", file1)); 534 if (!noaction) 535 (void)unlink(file1); 536 } 537 } 538 539 /* Move down log files. */ 540 for (i = log->numhist - 1; i > 0; i--) { 541 for (j = 0; j < (int)__arraycount(compress); j++) { 542 snprintf(file1, sizeof(file1), "%s.%d%s", log->logfile, 543 i - 1, compress[j].suffix); 544 snprintf(file2, sizeof(file2), "%s.%d%s", log->logfile, 545 i, compress[j].suffix); 546 k = lstat(file1, &st); 547 if (!k) break; 548 } 549 if (k) continue; 550 551 PRINFO(("mv %s %s\n", file1, file2)); 552 if (!noaction) 553 if (rename(file1, file2)) 554 err(EXIT_FAILURE, "%s", file1); 555 PRINFO(("chmod %o %s\n", log->mode, file2)); 556 if (!noaction) 557 if (chmod(file2, log->mode)) 558 err(EXIT_FAILURE, "%s", file2); 559 PRINFO(("chown %d:%d %s\n", log->uid, log->gid, 560 file2)); 561 if (!noaction) 562 if (chown(file2, log->uid, log->gid)) 563 err(EXIT_FAILURE, "%s", file2); 564 } 565 566 /* 567 * If a historical log file isn't compressed, and 'z' has been 568 * specified, compress it. (This is convenient, but is also needed 569 * if 'p' has been specified.) It should be noted that gzip(1) 570 * preserves file ownership and file mode. 571 */ 572 if (ziptype) { 573 for (i = (log->flags & CE_PLAIN0) != 0; i < log->numhist; i++) { 574 snprintf(file1, sizeof(file1), "%s.%d", log->logfile, i); 575 if (lstat(file1, &st) != 0) 576 continue; 577 snprintf(file2, sizeof(file2), "%s%s", file1, 578 compress[ziptype].suffix); 579 if (lstat(file2, &st) == 0) 580 continue; 581 log_compress(log, file1); 582 } 583 } 584 log_get_format(log); 585 log_trimmed(log); 586 587 /* Create the historical log file if we're maintaining history. */ 588 if (log->numhist == 0) { 589 PRINFO(("rm -f %s\n", log->logfile)); 590 if (!noaction) 591 if (unlink(log->logfile)) 592 err(EXIT_FAILURE, "%s", log->logfile); 593 } else { 594 (void)snprintf(file1, sizeof(file1), "%s.0", log->logfile); 595 PRINFO(("mv %s %s\n", log->logfile, file1)); 596 if (!noaction) 597 if (rename(log->logfile, file1)) 598 err(EXIT_FAILURE, "%s", log->logfile); 599 } 600 601 PRINFO(("(create new log)\n")); 602 log_create(log); 603 log_trimmed(log); 604 605 /* Set the correct permissions on the log. */ 606 PRINFO(("chmod %o %s\n", log->mode, log->logfile)); 607 if (!noaction) 608 if (chmod(log->logfile, log->mode)) 609 err(EXIT_FAILURE, "%s", log->logfile); 610 611 /* Do we need to signal a daemon? */ 612 if ((log->flags & CE_NOSIGNAL) == 0) { 613 if (log->pidfile[0] != '\0') 614 pid = readpidfile(log->pidfile); 615 else 616 pid = readpidfile(_PATH_SYSLOGDPID); 617 618 if (pid != (pid_t)-1) { 619 PRINFO(("kill -%s %lu\n", 620 sys_signame[log->signum], (u_long)pid)); 621 if (!noaction) 622 if (kill(pid, log->signum)) 623 warn("kill"); 624 } 625 } 626 627 /* If the newest historical log is to be compressed, do it here. */ 628 if (ziptype && !(log->flags & CE_PLAIN0) && log->numhist != 0) { 629 snprintf(file1, sizeof(file1), "%s.0", log->logfile); 630 if ((log->flags & CE_NOSIGNAL) == 0) { 631 PRINFO(("sleep for 10 seconds before compressing...\n")); 632 (void)sleep(10); 633 } 634 log_compress(log, file1); 635 } 636 } 637 638 static void 639 log_get_format(struct conf_entry *log) 640 { 641 FILE *fd; 642 char *line; 643 size_t linelen; 644 645 if ((log->flags & CE_BINARY) != 0) 646 return; 647 PRINFO(("(read line format of %s)\n", log->logfile)); 648 if (noaction) 649 return; 650 651 if ((fd = fopen(log->logfile, "r")) == NULL) 652 return; 653 654 /* read 2nd line */ 655 line = fgetln(fd, &linelen); 656 if ((line = fgetln(fd, &linelen)) != NULL 657 && line[10] == 'T') 658 log->flags |= CE_SYSLPROTOCOL; 659 (void)fclose(fd); 660 } 661 662 /* 663 * Write an entry to the log file recording the fact that it was trimmed. 664 */ 665 static void 666 log_trimmed(struct conf_entry *log) 667 { 668 FILE *fd; 669 time_t now; 670 const char *daytime; 671 const char trim_message[] = "log file turned over"; 672 673 if ((log->flags & CE_BINARY) != 0) 674 return; 675 PRINFO(("(append rotation notice to %s)\n", log->logfile)); 676 if (noaction) 677 return; 678 679 if ((fd = fopen(log->logfile, "at")) == NULL) 680 err(EXIT_FAILURE, "%s", log->logfile); 681 682 if ((log->flags & CE_SYSLPROTOCOL) == 0) { 683 char shorthostname[MAXHOSTNAMELEN]; 684 char *p; 685 686 /* Truncate domain. */ 687 (void)strlcpy(shorthostname, hostname, sizeof(shorthostname)); 688 if ((p = strchr(shorthostname, '.')) != NULL) 689 *p = '\0'; 690 691 now = time(NULL); 692 daytime = p = ctime(&now) + 4; 693 p[15] = '\0'; 694 695 (void)fprintf(fd, "%s %s newsyslog[%lu]: %s\n", 696 daytime, hostname, (u_long)getpid(), trim_message); 697 } else { 698 struct tm *tmnow; 699 struct timeval tv; 700 char timestamp[35]; 701 unsigned i, j; 702 703 if (gettimeofday(&tv, NULL) == -1) { 704 daytime = "-"; 705 } else { 706 tzset(); 707 now = (time_t) tv.tv_sec; 708 tmnow = localtime(&now); 709 710 i = strftime(timestamp, sizeof(timestamp), 711 "%FT%T", tmnow); 712 i += snprintf(timestamp+i, sizeof(timestamp)-i, 713 ".%06ld", (long)tv.tv_usec); 714 i += j = strftime(timestamp+i, sizeof(timestamp)-i-1, 715 "%z", tmnow); 716 /* strftime gives eg. "+0200", but we need "+02:00" */ 717 if (j == 5) { 718 timestamp[i+1] = timestamp[i]; 719 timestamp[i] = timestamp[i-1]; 720 timestamp[i-1] = timestamp[i-2]; 721 timestamp[i-2] = ':'; 722 i += 1; 723 } 724 daytime = timestamp; 725 } 726 (void)fprintf(fd, "%s %s newsyslog %lu - - %s\n", 727 daytime, hostname, (u_long)getpid(), trim_message); 728 729 } 730 (void)fclose(fd); 731 } 732 733 /* 734 * Create a new log file. 735 */ 736 static void 737 log_create(struct conf_entry *log) 738 { 739 int fd; 740 741 if (noaction) 742 return; 743 744 if ((fd = creat(log->logfile, log->mode)) < 0) 745 err(EXIT_FAILURE, "%s", log->logfile); 746 if (fchown(fd, log->uid, log->gid) < 0) 747 err(EXIT_FAILURE, "%s", log->logfile); 748 (void)close(fd); 749 } 750 751 /* 752 * Fork off gzip(1) to compress a log file. This routine takes an 753 * additional string argument (the name of the file to compress): it is also 754 * used to compress historical log files other than the newest. 755 */ 756 static void 757 log_compress(struct conf_entry *log, const char *fn) 758 { 759 char tmp[MAXPATHLEN]; 760 761 PRINFO(("%s %s %s\n", compress[ziptype].path, compress[ziptype].args, 762 fn)); 763 if (!noaction) { 764 pid_t pid; 765 int status; 766 767 if ((pid = vfork()) < 0) 768 err(EXIT_FAILURE, "vfork"); 769 else if (pid == 0) { 770 (void)execl(compress[ziptype].path, 771 compress[ziptype].path, compress[ziptype].args, fn, 772 NULL); 773 _exit(EXIT_FAILURE); 774 } 775 while (waitpid(pid, &status, 0) != pid); 776 777 if (!WIFEXITED(status) || (WEXITSTATUS(status) != 0)) 778 errx(EXIT_FAILURE, "%s failed", compress[ziptype].path); 779 } 780 781 (void)snprintf(tmp, sizeof(tmp), "%s%s", fn, compress[ziptype].suffix); 782 PRINFO(("chown %d:%d %s\n", log->uid, log->gid, tmp)); 783 if (!noaction) 784 if (chown(tmp, log->uid, log->gid)) 785 err(EXIT_FAILURE, "%s", tmp); 786 } 787 788 /* 789 * Display program usage information. 790 */ 791 static void 792 usage(void) 793 { 794 795 (void)fprintf(stderr, 796 "Usage: %s [-nrsvF] [-f config-file] [file ...]\n", getprogname()); 797 exit(EXIT_FAILURE); 798 } 799 800 /* 801 * Return non-zero if a string represents a decimal value. 802 */ 803 static int 804 isnumber(const char *string) 805 { 806 807 while (isdigit((unsigned char)*string)) 808 string++; 809 810 return *string == '\0'; 811 } 812 813 /* 814 * Given a signal name, attempt to find the corresponding signal number. 815 */ 816 static int 817 getsig(const char *sig) 818 { 819 char *p; 820 int n; 821 822 if (isnumber(sig)) { 823 n = (int)strtol(sig, &p, 0); 824 if (p != '\0' || n < 0 || n >= NSIG) 825 return -1; 826 return n; 827 } 828 829 if (strncasecmp(sig, "SIG", 3) == 0) 830 sig += 3; 831 for (n = 1; n < NSIG; n++) 832 if (strcasecmp(sys_signame[n], sig) == 0) 833 return n; 834 return -1; 835 } 836 837 /* 838 * Given a path to a PID file, return the PID contained within. 839 */ 840 static pid_t 841 readpidfile(const char *file) 842 { 843 FILE *fd; 844 char line[BUFSIZ]; 845 pid_t pid; 846 847 #ifdef notyet 848 if (file[0] != '/') 849 (void)snprintf(tmp, sizeof(tmp), "%s%s", _PATH_VARRUN, file); 850 else 851 (void)strlcpy(tmp, file, sizeof(tmp)); 852 #endif 853 854 if ((fd = fopen(file, "r")) == NULL) { 855 warn("%s", file); 856 return (pid_t)-1; 857 } 858 859 if (fgets(line, sizeof(line) - 1, fd) != NULL) { 860 line[sizeof(line) - 1] = '\0'; 861 pid = (pid_t)strtol(line, NULL, 0); 862 } else { 863 warnx("unable to read %s", file); 864 pid = (pid_t)-1; 865 } 866 867 (void)fclose(fd); 868 return pid; 869 } 870 871 /* 872 * Parse a user:group specification. 873 * 874 * XXX This is over the top for newsyslog(8). It should be moved to libutil. 875 */ 876 int 877 parse_userspec(const char *name, struct passwd **pw, struct group **gr) 878 { 879 char buf[MAXLOGNAME * 2 + 2], *group; 880 881 (void)strlcpy(buf, name, sizeof(buf)); 882 *gr = NULL; 883 884 /* 885 * Before attempting to use '.' as a separator, see if the whole 886 * string resolves as a user name. 887 */ 888 if ((*pw = getpwnam(buf)) != NULL) { 889 *gr = getgrgid((*pw)->pw_gid); 890 return (0); 891 } 892 893 /* Split the user and group name. */ 894 if ((group = strchr(buf, ':')) != NULL || 895 (group = strchr(buf, '.')) != NULL) 896 *group++ = '\0'; 897 898 if (isnumber(buf)) 899 *pw = getpwuid((uid_t)atoi(buf)); 900 else 901 *pw = getpwnam(buf); 902 903 /* 904 * Find the group. If a group wasn't specified, use the user's 905 * `natural' group. We get to this point even if no user was found. 906 * This is to allow the caller to get a better idea of what went 907 * wrong, if anything. 908 */ 909 if (group == NULL || *group == '\0') { 910 if (*pw == NULL) 911 return -1; 912 *gr = getgrgid((*pw)->pw_gid); 913 } else if (isnumber(group)) 914 *gr = getgrgid((gid_t)atoi(group)); 915 else 916 *gr = getgrnam(group); 917 918 return *pw != NULL && *gr != NULL ? 0 : -1; 919 } 920 921 /* 922 * Parse a cyclic time specification, the format is as follows: 923 * 924 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] 925 * 926 * to rotate a log file cyclic at 927 * 928 * - every day (D) within a specific hour (hh) (hh = 0...23) 929 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) 930 * - once a month (M) at a specific day (d) (d = 1..31,l|L) 931 * 932 * We don't accept a timezone specification; missing fields are defaulted to 933 * the current date but time zero. 934 */ 935 static time_t 936 parse_dwm(char *s) 937 { 938 char *t; 939 struct tm tm, *tmp; 940 long ul; 941 time_t now; 942 static int mtab[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 943 int wmseen, dseen, nd, save; 944 945 wmseen = 0; 946 dseen = 0; 947 948 now = time(NULL); 949 tmp = localtime(&now); 950 tm = *tmp; 951 952 /* Set no. of days per month */ 953 nd = mtab[tm.tm_mon]; 954 955 if (tm.tm_mon == 1 && 956 ((tm.tm_year + 1900) % 4 == 0) && 957 ((tm.tm_year + 1900) % 100 != 0) && 958 ((tm.tm_year + 1900) % 400 == 0)) 959 nd++; /* leap year, 29 days in february */ 960 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 961 962 for (;;) { 963 switch (*s) { 964 case 'D': 965 if (dseen) 966 return (time_t)-1; 967 dseen++; 968 s++; 969 ul = strtol(s, &t, 10); 970 if (ul > 23 || ul < 0) 971 return (time_t)-1; 972 tm.tm_hour = ul; 973 break; 974 975 case 'W': 976 if (wmseen) 977 return (time_t)-1; 978 wmseen++; 979 s++; 980 ul = strtol(s, &t, 10); 981 if (ul > 6 || ul < 0) 982 return (-1); 983 if (ul != tm.tm_wday) { 984 if (ul < tm.tm_wday) { 985 save = 6 - tm.tm_wday; 986 save += (ul + 1); 987 } else 988 save = ul - tm.tm_wday; 989 tm.tm_mday += save; 990 991 if (tm.tm_mday > nd) { 992 tm.tm_mon++; 993 tm.tm_mday = tm.tm_mday - nd; 994 } 995 } 996 break; 997 998 case 'M': 999 if (wmseen) 1000 return (time_t)-1; 1001 wmseen++; 1002 s++; 1003 if (tolower((unsigned char)*s) == 'l') { 1004 tm.tm_mday = nd; 1005 s++; 1006 t = s; 1007 } else { 1008 ul = strtol(s, &t, 10); 1009 if (ul < 1 || ul > 31) 1010 return (time_t)-1; 1011 1012 if (ul > nd) 1013 return (time_t)-1; 1014 tm.tm_mday = ul; 1015 } 1016 break; 1017 1018 default: 1019 return (time_t)-1; 1020 } 1021 1022 if (*t == '\0' || isspace((unsigned char)*t)) 1023 break; 1024 else 1025 s = t; 1026 } 1027 1028 return mktime(&tm); 1029 } 1030 1031 /* 1032 * Parse a limited subset of ISO 8601. The specific format is as follows: 1033 * 1034 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 1035 * 1036 * We don't accept a timezone specification; missing fields (including 1037 * timezone) are defaulted to the current date but time zero. 1038 */ 1039 static time_t 1040 parse_iso8601(char *s) 1041 { 1042 char *t; 1043 struct tm tm, *tmp; 1044 u_long ul; 1045 time_t now; 1046 1047 now = time(NULL); 1048 tmp = localtime(&now); 1049 tm = *tmp; 1050 1051 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1052 1053 ul = strtoul(s, &t, 10); 1054 if (*t != '\0' && *t != 'T') 1055 return (time_t)-1; 1056 1057 /* 1058 * Now t points either to the end of the string (if no time was 1059 * provided) or to the letter `T' which separates date and time in 1060 * ISO 8601. The pointer arithmetic is the same for either case. 1061 */ 1062 switch (t - s) { 1063 case 8: 1064 tm.tm_year = ((ul / 1000000) - 19) * 100; 1065 ul = ul % 1000000; 1066 /* FALLTHROUGH */ 1067 case 6: 1068 tm.tm_year = tm.tm_year - (tm.tm_year % 100); 1069 tm.tm_year += ul / 10000; 1070 ul = ul % 10000; 1071 /* FALLTHROUGH */ 1072 case 4: 1073 tm.tm_mon = (ul / 100) - 1; 1074 ul = ul % 100; 1075 /* FALLTHROUGH */ 1076 case 2: 1077 tm.tm_mday = ul; 1078 /* FALLTHROUGH */ 1079 case 0: 1080 break; 1081 default: 1082 return (time_t)-1; 1083 } 1084 1085 /* Sanity check */ 1086 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 || 1087 tm.tm_mday < 1 || tm.tm_mday > 31) 1088 return (time_t)-1; 1089 1090 if (*t != '\0') { 1091 s = ++t; 1092 ul = strtoul(s, &t, 10); 1093 if (*t != '\0' && !isspace((unsigned char)*t)) 1094 return (time_t)-1; 1095 1096 switch (t - s) { 1097 case 6: 1098 tm.tm_sec = ul % 100; 1099 ul /= 100; 1100 /* FALLTHROUGH */ 1101 case 4: 1102 tm.tm_min = ul % 100; 1103 ul /= 100; 1104 /* FALLTHROUGH */ 1105 case 2: 1106 tm.tm_hour = ul; 1107 /* FALLTHROUGH */ 1108 case 0: 1109 break; 1110 default: 1111 return (time_t)-1; 1112 } 1113 1114 /* Sanity check */ 1115 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 || 1116 tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 1117 return (time_t)-1; 1118 } 1119 1120 return mktime(&tm); 1121 } 1122