1 /* $NetBSD: newsyslog.c,v 1.58 2009/06/20 19:34:19 christos 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.58 2009/06/20 19:34:19 christos 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 /* 540 * If a historical log file isn't compressed, and 'z' has been 541 * specified, compress it. (This is convenient, but is also needed 542 * if 'p' has been specified.) It should be noted that gzip(1) 543 * preserves file ownership and file mode. 544 */ 545 if (ziptype) { 546 for (i = 0; i < log->numhist; i++) { 547 snprintf(file1, sizeof(file1), "%s.%d", log->logfile, i); 548 if (lstat(file1, &st) != 0) 549 continue; 550 snprintf(file2, sizeof(file2), "%s%s", file1, 551 compress[ziptype].suffix); 552 if (lstat(file2, &st) == 0) 553 continue; 554 log_compress(log, file1); 555 } 556 } 557 558 /* Move down log files. */ 559 for (i = log->numhist - 1; i > 0; i--) { 560 for (j = 0; j < (int)__arraycount(compress); j++) { 561 snprintf(file1, sizeof(file1), "%s.%d%s", log->logfile, 562 i - 1, compress[j].suffix); 563 snprintf(file2, sizeof(file2), "%s.%d%s", log->logfile, 564 i, compress[j].suffix); 565 k = lstat(file1, &st); 566 if (!k) break; 567 } 568 if (k) continue; 569 570 PRINFO(("mv %s %s\n", file1, file2)); 571 if (!noaction) 572 if (rename(file1, file2)) 573 err(EXIT_FAILURE, "%s", file1); 574 PRINFO(("chmod %o %s\n", log->mode, file2)); 575 if (!noaction) 576 if (chmod(file2, log->mode)) 577 err(EXIT_FAILURE, "%s", file2); 578 PRINFO(("chown %d:%d %s\n", log->uid, log->gid, 579 file2)); 580 if (!noaction) 581 if (chown(file2, log->uid, log->gid)) 582 err(EXIT_FAILURE, "%s", file2); 583 } 584 585 log_get_format(log); 586 log_trimmed(log); 587 588 /* Create the historical log file if we're maintaining history. */ 589 if (log->numhist == 0) { 590 PRINFO(("rm -f %s\n", log->logfile)); 591 if (!noaction) 592 if (unlink(log->logfile)) 593 err(EXIT_FAILURE, "%s", log->logfile); 594 } else { 595 (void)snprintf(file1, sizeof(file1), "%s.0", log->logfile); 596 PRINFO(("mv %s %s\n", log->logfile, file1)); 597 if (!noaction) 598 if (rename(log->logfile, file1)) 599 err(EXIT_FAILURE, "%s", log->logfile); 600 } 601 602 PRINFO(("(create new log)\n")); 603 log_create(log); 604 log_trimmed(log); 605 606 /* Set the correct permissions on the log. */ 607 PRINFO(("chmod %o %s\n", log->mode, log->logfile)); 608 if (!noaction) 609 if (chmod(log->logfile, log->mode)) 610 err(EXIT_FAILURE, "%s", log->logfile); 611 612 /* Do we need to signal a daemon? */ 613 if ((log->flags & CE_NOSIGNAL) == 0) { 614 if (log->pidfile[0] != '\0') 615 pid = readpidfile(log->pidfile); 616 else 617 pid = readpidfile(_PATH_SYSLOGDPID); 618 619 if (pid != (pid_t)-1) { 620 PRINFO(("kill -%s %lu\n", 621 sys_signame[log->signum], (u_long)pid)); 622 if (!noaction) 623 if (kill(pid, log->signum)) 624 warn("kill"); 625 } 626 } 627 628 /* If the newest historical log is to be compressed, do it here. */ 629 if (ziptype && !(log->flags & CE_PLAIN0) && log->numhist != 0) { 630 snprintf(file1, sizeof(file1), "%s.0", log->logfile); 631 if ((log->flags & CE_NOSIGNAL) == 0) { 632 PRINFO(("sleep for 10 seconds before compressing...\n")); 633 (void)sleep(10); 634 } 635 log_compress(log, file1); 636 } 637 } 638 639 static void 640 log_get_format(struct conf_entry *log) 641 { 642 FILE *fd; 643 char *line; 644 size_t linelen; 645 646 if ((log->flags & CE_BINARY) != 0) 647 return; 648 PRINFO(("(read line format of %s)\n", log->logfile)); 649 if (noaction) 650 return; 651 652 if ((fd = fopen(log->logfile, "r")) == NULL) 653 return; 654 655 /* read 2nd line */ 656 line = fgetln(fd, &linelen); 657 if ((line = fgetln(fd, &linelen)) != NULL 658 && line[10] == 'T') 659 log->flags |= CE_SYSLPROTOCOL; 660 (void)fclose(fd); 661 } 662 663 /* 664 * Write an entry to the log file recording the fact that it was trimmed. 665 */ 666 static void 667 log_trimmed(struct conf_entry *log) 668 { 669 FILE *fd; 670 time_t now; 671 const char *daytime; 672 const char trim_message[] = "log file turned over"; 673 674 if ((log->flags & CE_BINARY) != 0) 675 return; 676 PRINFO(("(append rotation notice to %s)\n", log->logfile)); 677 if (noaction) 678 return; 679 680 if ((fd = fopen(log->logfile, "at")) == NULL) 681 err(EXIT_FAILURE, "%s", log->logfile); 682 683 if ((log->flags & CE_SYSLPROTOCOL) == 0) { 684 char shorthostname[MAXHOSTNAMELEN]; 685 char *p; 686 687 /* Truncate domain. */ 688 (void)strlcpy(shorthostname, hostname, sizeof(shorthostname)); 689 if ((p = strchr(shorthostname, '.')) != NULL) 690 *p = '\0'; 691 692 now = time(NULL); 693 daytime = p = ctime(&now) + 4; 694 p[15] = '\0'; 695 696 (void)fprintf(fd, "%s %s newsyslog[%lu]: %s\n", 697 daytime, hostname, (u_long)getpid(), trim_message); 698 } else { 699 struct tm *tmnow; 700 struct timeval tv; 701 char timestamp[35]; 702 unsigned i, j; 703 704 if (gettimeofday(&tv, NULL) == -1) { 705 daytime = "-"; 706 } else { 707 tzset(); 708 now = (time_t) tv.tv_sec; 709 tmnow = localtime(&now); 710 711 i = strftime(timestamp, sizeof(timestamp), 712 "%FT%T", tmnow); 713 i += snprintf(timestamp+i, sizeof(timestamp)-i, 714 ".%06ld", (long)tv.tv_usec); 715 i += j = strftime(timestamp+i, sizeof(timestamp)-i-1, 716 "%z", tmnow); 717 /* strftime gives eg. "+0200", but we need "+02:00" */ 718 if (j == 5) { 719 timestamp[i+1] = timestamp[i]; 720 timestamp[i] = timestamp[i-1]; 721 timestamp[i-1] = timestamp[i-2]; 722 timestamp[i-2] = ':'; 723 i += 1; 724 } 725 daytime = timestamp; 726 } 727 (void)fprintf(fd, "%s %s newsyslog %lu - - %s\n", 728 daytime, hostname, (u_long)getpid(), trim_message); 729 730 } 731 (void)fclose(fd); 732 } 733 734 /* 735 * Create a new log file. 736 */ 737 static void 738 log_create(struct conf_entry *log) 739 { 740 int fd; 741 742 if (noaction) 743 return; 744 745 if ((fd = creat(log->logfile, log->mode)) < 0) 746 err(EXIT_FAILURE, "%s", log->logfile); 747 if (fchown(fd, log->uid, log->gid) < 0) 748 err(EXIT_FAILURE, "%s", log->logfile); 749 (void)close(fd); 750 } 751 752 /* 753 * Fork off gzip(1) to compress a log file. This routine takes an 754 * additional string argument (the name of the file to compress): it is also 755 * used to compress historical log files other than the newest. 756 */ 757 static void 758 log_compress(struct conf_entry *log, const char *fn) 759 { 760 char tmp[MAXPATHLEN]; 761 762 PRINFO(("%s %s %s\n", compress[ziptype].path, compress[ziptype].args, 763 fn)); 764 if (!noaction) { 765 pid_t pid; 766 int status; 767 768 if ((pid = vfork()) < 0) 769 err(EXIT_FAILURE, "vfork"); 770 else if (pid == 0) { 771 (void)execl(compress[ziptype].path, 772 compress[ziptype].path, compress[ziptype].args, fn, 773 NULL); 774 _exit(EXIT_FAILURE); 775 } 776 while (waitpid(pid, &status, 0) != pid); 777 778 if (!WIFEXITED(status) || (WEXITSTATUS(status) != 0)) 779 errx(EXIT_FAILURE, "%s failed", compress[ziptype].path); 780 } 781 782 (void)snprintf(tmp, sizeof(tmp), "%s%s", fn, compress[ziptype].suffix); 783 PRINFO(("chown %d:%d %s\n", log->uid, log->gid, tmp)); 784 if (!noaction) 785 if (chown(tmp, log->uid, log->gid)) 786 err(EXIT_FAILURE, "%s", tmp); 787 } 788 789 /* 790 * Display program usage information. 791 */ 792 static void 793 usage(void) 794 { 795 796 (void)fprintf(stderr, 797 "Usage: %s [-nrsvF] [-f config-file] [file ...]\n", getprogname()); 798 exit(EXIT_FAILURE); 799 } 800 801 /* 802 * Return non-zero if a string represents a decimal value. 803 */ 804 static int 805 isnumber(const char *string) 806 { 807 808 while (isdigit((unsigned char)*string)) 809 string++; 810 811 return *string == '\0'; 812 } 813 814 /* 815 * Given a signal name, attempt to find the corresponding signal number. 816 */ 817 static int 818 getsig(const char *sig) 819 { 820 char *p; 821 int n; 822 823 if (isnumber(sig)) { 824 n = (int)strtol(sig, &p, 0); 825 if (p != '\0' || n < 0 || n >= NSIG) 826 return -1; 827 return n; 828 } 829 830 if (strncasecmp(sig, "SIG", 3) == 0) 831 sig += 3; 832 for (n = 1; n < NSIG; n++) 833 if (strcasecmp(sys_signame[n], sig) == 0) 834 return n; 835 return -1; 836 } 837 838 /* 839 * Given a path to a PID file, return the PID contained within. 840 */ 841 static pid_t 842 readpidfile(const char *file) 843 { 844 FILE *fd; 845 char line[BUFSIZ]; 846 pid_t pid; 847 848 #ifdef notyet 849 if (file[0] != '/') 850 (void)snprintf(tmp, sizeof(tmp), "%s%s", _PATH_VARRUN, file); 851 else 852 (void)strlcpy(tmp, file, sizeof(tmp)); 853 #endif 854 855 if ((fd = fopen(file, "r")) == NULL) { 856 warn("%s", file); 857 return (pid_t)-1; 858 } 859 860 if (fgets(line, sizeof(line) - 1, fd) != NULL) { 861 line[sizeof(line) - 1] = '\0'; 862 pid = (pid_t)strtol(line, NULL, 0); 863 } else { 864 warnx("unable to read %s", file); 865 pid = (pid_t)-1; 866 } 867 868 (void)fclose(fd); 869 return pid; 870 } 871 872 /* 873 * Parse a user:group specification. 874 * 875 * XXX This is over the top for newsyslog(8). It should be moved to libutil. 876 */ 877 int 878 parse_userspec(const char *name, struct passwd **pw, struct group **gr) 879 { 880 char buf[MAXLOGNAME * 2 + 2], *group; 881 882 (void)strlcpy(buf, name, sizeof(buf)); 883 *gr = NULL; 884 885 /* 886 * Before attempting to use '.' as a separator, see if the whole 887 * string resolves as a user name. 888 */ 889 if ((*pw = getpwnam(buf)) != NULL) { 890 *gr = getgrgid((*pw)->pw_gid); 891 return (0); 892 } 893 894 /* Split the user and group name. */ 895 if ((group = strchr(buf, ':')) != NULL || 896 (group = strchr(buf, '.')) != NULL) 897 *group++ = '\0'; 898 899 if (isnumber(buf)) 900 *pw = getpwuid((uid_t)atoi(buf)); 901 else 902 *pw = getpwnam(buf); 903 904 /* 905 * Find the group. If a group wasn't specified, use the user's 906 * `natural' group. We get to this point even if no user was found. 907 * This is to allow the caller to get a better idea of what went 908 * wrong, if anything. 909 */ 910 if (group == NULL || *group == '\0') { 911 if (*pw == NULL) 912 return -1; 913 *gr = getgrgid((*pw)->pw_gid); 914 } else if (isnumber(group)) 915 *gr = getgrgid((gid_t)atoi(group)); 916 else 917 *gr = getgrnam(group); 918 919 return *pw != NULL && *gr != NULL ? 0 : -1; 920 } 921 922 /* 923 * Parse a cyclic time specification, the format is as follows: 924 * 925 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] 926 * 927 * to rotate a log file cyclic at 928 * 929 * - every day (D) within a specific hour (hh) (hh = 0...23) 930 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) 931 * - once a month (M) at a specific day (d) (d = 1..31,l|L) 932 * 933 * We don't accept a timezone specification; missing fields are defaulted to 934 * the current date but time zero. 935 */ 936 static time_t 937 parse_dwm(char *s) 938 { 939 char *t; 940 struct tm tm, *tmp; 941 long ul; 942 time_t now; 943 static int mtab[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 944 int wmseen, dseen, nd, save; 945 946 wmseen = 0; 947 dseen = 0; 948 949 now = time(NULL); 950 tmp = localtime(&now); 951 tm = *tmp; 952 953 /* Set no. of days per month */ 954 nd = mtab[tm.tm_mon]; 955 956 if (tm.tm_mon == 1 && 957 ((tm.tm_year + 1900) % 4 == 0) && 958 ((tm.tm_year + 1900) % 100 != 0) && 959 ((tm.tm_year + 1900) % 400 == 0)) 960 nd++; /* leap year, 29 days in february */ 961 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 962 963 for (;;) { 964 switch (*s) { 965 case 'D': 966 if (dseen) 967 return (time_t)-1; 968 dseen++; 969 s++; 970 ul = strtol(s, &t, 10); 971 if (ul > 23 || ul < 0) 972 return (time_t)-1; 973 tm.tm_hour = ul; 974 break; 975 976 case 'W': 977 if (wmseen) 978 return (time_t)-1; 979 wmseen++; 980 s++; 981 ul = strtol(s, &t, 10); 982 if (ul > 6 || ul < 0) 983 return (-1); 984 if (ul != tm.tm_wday) { 985 if (ul < tm.tm_wday) { 986 save = 6 - tm.tm_wday; 987 save += (ul + 1); 988 } else 989 save = ul - tm.tm_wday; 990 tm.tm_mday += save; 991 992 if (tm.tm_mday > nd) { 993 tm.tm_mon++; 994 tm.tm_mday = tm.tm_mday - nd; 995 } 996 } 997 break; 998 999 case 'M': 1000 if (wmseen) 1001 return (time_t)-1; 1002 wmseen++; 1003 s++; 1004 if (tolower((unsigned char)*s) == 'l') { 1005 tm.tm_mday = nd; 1006 s++; 1007 t = s; 1008 } else { 1009 ul = strtol(s, &t, 10); 1010 if (ul < 1 || ul > 31) 1011 return (time_t)-1; 1012 1013 if (ul > nd) 1014 return (time_t)-1; 1015 tm.tm_mday = ul; 1016 } 1017 break; 1018 1019 default: 1020 return (time_t)-1; 1021 } 1022 1023 if (*t == '\0' || isspace((unsigned char)*t)) 1024 break; 1025 else 1026 s = t; 1027 } 1028 1029 return mktime(&tm); 1030 } 1031 1032 /* 1033 * Parse a limited subset of ISO 8601. The specific format is as follows: 1034 * 1035 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 1036 * 1037 * We don't accept a timezone specification; missing fields (including 1038 * timezone) are defaulted to the current date but time zero. 1039 */ 1040 static time_t 1041 parse_iso8601(char *s) 1042 { 1043 char *t; 1044 struct tm tm, *tmp; 1045 u_long ul; 1046 time_t now; 1047 1048 now = time(NULL); 1049 tmp = localtime(&now); 1050 tm = *tmp; 1051 1052 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1053 1054 ul = strtoul(s, &t, 10); 1055 if (*t != '\0' && *t != 'T') 1056 return (time_t)-1; 1057 1058 /* 1059 * Now t points either to the end of the string (if no time was 1060 * provided) or to the letter `T' which separates date and time in 1061 * ISO 8601. The pointer arithmetic is the same for either case. 1062 */ 1063 switch (t - s) { 1064 case 8: 1065 tm.tm_year = ((ul / 1000000) - 19) * 100; 1066 ul = ul % 1000000; 1067 /* FALLTHROUGH */ 1068 case 6: 1069 tm.tm_year = tm.tm_year - (tm.tm_year % 100); 1070 tm.tm_year += ul / 10000; 1071 ul = ul % 10000; 1072 /* FALLTHROUGH */ 1073 case 4: 1074 tm.tm_mon = (ul / 100) - 1; 1075 ul = ul % 100; 1076 /* FALLTHROUGH */ 1077 case 2: 1078 tm.tm_mday = ul; 1079 /* FALLTHROUGH */ 1080 case 0: 1081 break; 1082 default: 1083 return (time_t)-1; 1084 } 1085 1086 /* Sanity check */ 1087 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 || 1088 tm.tm_mday < 1 || tm.tm_mday > 31) 1089 return (time_t)-1; 1090 1091 if (*t != '\0') { 1092 s = ++t; 1093 ul = strtoul(s, &t, 10); 1094 if (*t != '\0' && !isspace((unsigned char)*t)) 1095 return (time_t)-1; 1096 1097 switch (t - s) { 1098 case 6: 1099 tm.tm_sec = ul % 100; 1100 ul /= 100; 1101 /* FALLTHROUGH */ 1102 case 4: 1103 tm.tm_min = ul % 100; 1104 ul /= 100; 1105 /* FALLTHROUGH */ 1106 case 2: 1107 tm.tm_hour = ul; 1108 /* FALLTHROUGH */ 1109 case 0: 1110 break; 1111 default: 1112 return (time_t)-1; 1113 } 1114 1115 /* Sanity check */ 1116 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 || 1117 tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 1118 return (time_t)-1; 1119 } 1120 1121 return mktime(&tm); 1122 } 1123