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