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