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