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