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