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