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