1 /* 2 * This file contains changes from the Open Software Foundation. 3 */ 4 5 /* 6 7 Copyright 1988, 1989 by the Massachusetts Institute of Technology 8 9 Permission to use, copy, modify, and distribute this software 10 and its documentation for any purpose and without fee is 11 hereby granted, provided that the above copyright notice 12 appear in all copies and that both that copyright notice and 13 this permission notice appear in supporting documentation, 14 and that the names of M.I.T. and the M.I.T. S.I.P.B. not be 15 used in advertising or publicity pertaining to distribution 16 of the software without specific, written prior permission. 17 M.I.T. and the M.I.T. S.I.P.B. make no representations about 18 the suitability of this software for any purpose. It is 19 provided "as is" without express or implied warranty. 20 21 */ 22 23 /* 24 * newsyslog - roll over selected logs at the appropriate time, 25 * keeping the a specified number of backup files around. 26 */ 27 28 #ifndef lint 29 static const char rcsid[] = 30 "$FreeBSD$"; 31 #endif /* not lint */ 32 33 #define OSF 34 #ifndef COMPRESS_POSTFIX 35 #define COMPRESS_POSTFIX ".gz" 36 #endif 37 38 #include <ctype.h> 39 #include <err.h> 40 #include <fcntl.h> 41 #include <grp.h> 42 #include <paths.h> 43 #include <pwd.h> 44 #include <signal.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <time.h> 49 #include <unistd.h> 50 #include <sys/types.h> 51 #include <sys/stat.h> 52 #include <sys/param.h> 53 #include <sys/wait.h> 54 55 #include "pathnames.h" 56 57 #define kbytes(size) (((size) + 1023) >> 10) 58 #ifdef _IBMR2 59 /* Calculates (db * DEV_BSIZE) */ 60 #define dbtob(db) ((unsigned)(db) << UBSHIFT) 61 #endif 62 63 #define CE_COMPACT 1 /* Compact the achived log files */ 64 #define CE_BINARY 2 /* Logfile is in binary, don't add */ 65 /* status messages */ 66 #define CE_TRIMAT 4 /* trim at a specific time */ 67 68 #define NONE -1 69 70 struct conf_entry { 71 char *log; /* Name of the log */ 72 char *pid_file; /* PID file */ 73 int uid; /* Owner of log */ 74 int gid; /* Group of log */ 75 int numlogs; /* Number of logs to keep */ 76 int size; /* Size cutoff to trigger trimming the log */ 77 int hours; /* Hours between log trimming */ 78 time_t trim_at; /* Specific time to do trimming */ 79 int permissions; /* File permissions on the log */ 80 int flags; /* Flags (CE_COMPACT & CE_BINARY) */ 81 int sig; /* Signal to send */ 82 struct conf_entry *next; /* Linked list pointer */ 83 }; 84 85 int verbose = 0; /* Print out what's going on */ 86 int needroot = 1; /* Root privs are necessary */ 87 int noaction = 0; /* Don't do anything, just show it */ 88 int force = 0; /* Force the trim no matter what*/ 89 char *conf = _PATH_CONF; /* Configuration file to use */ 90 time_t timenow; 91 #define MIN_PID 5 92 #define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */ 93 char hostname[MAXHOSTNAMELEN+1]; /* hostname */ 94 char *daytime; /* timenow in human readable form */ 95 96 static struct conf_entry *parse_file(); 97 static char *sob(char *p); 98 static char *son(char *p); 99 static char *missing_field(char *p,char *errline); 100 static void do_entry(struct conf_entry *ent); 101 static void PRS(int argc,char **argv); 102 static void usage(); 103 static void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm,int owner_uid,int group_gid,int sig); 104 static int log_trim(char *log); 105 static void compress_log(char *log); 106 static int sizefile(char *file); 107 static int age_old_log(char *file); 108 static pid_t get_pid(char *pid_file); 109 static time_t parse8601(const char *s); 110 111 int main(argc,argv) 112 int argc; 113 char **argv; 114 { 115 struct conf_entry *p, *q; 116 117 PRS(argc,argv); 118 if (needroot && getuid() && geteuid()) 119 errx(1, "must have root privs"); 120 p = q = parse_file(); 121 122 while (p) { 123 do_entry(p); 124 p=p->next; 125 free((char *) q); 126 q=p; 127 } 128 return(0); 129 } 130 131 static void do_entry(ent) 132 struct conf_entry *ent; 133 134 { 135 int size, modtime; 136 char *pid_file; 137 138 if (verbose) { 139 if (ent->flags & CE_COMPACT) 140 printf("%s <%dZ>: ",ent->log,ent->numlogs); 141 else 142 printf("%s <%d>: ",ent->log,ent->numlogs); 143 } 144 size = sizefile(ent->log); 145 modtime = age_old_log(ent->log); 146 if (size < 0) { 147 if (verbose) 148 printf("does not exist.\n"); 149 } else { 150 if (ent->flags & CE_TRIMAT) { 151 if (timenow < ent->trim_at 152 || difftime(timenow, ent->trim_at) >= 60*60) { 153 if (verbose) 154 printf("--> will trim at %s", 155 ctime(&ent->trim_at)); 156 return; 157 } else if (verbose && ent->hours <= 0) { 158 printf("--> time is up\n"); 159 } 160 } 161 if (verbose && (ent->size > 0)) 162 printf("size (Kb): %d [%d] ", size, ent->size); 163 if (verbose && (ent->hours > 0)) 164 printf(" age (hr): %d [%d] ", modtime, ent->hours); 165 if (force || ((ent->size > 0) && (size >= ent->size)) || 166 (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) || 167 ((ent->hours > 0) && ((modtime >= ent->hours) 168 || (modtime < 0)))) { 169 if (verbose) 170 printf("--> trimming log....\n"); 171 if (noaction && !verbose) { 172 if (ent->flags & CE_COMPACT) 173 printf("%s <%dZ>: trimming\n", 174 ent->log,ent->numlogs); 175 else 176 printf("%s <%d>: trimming\n", 177 ent->log,ent->numlogs); 178 } 179 if (ent->pid_file) { 180 pid_file = ent->pid_file; 181 } else { 182 /* Only try to notify syslog if we are root */ 183 if (needroot) 184 pid_file = _PATH_SYSLOGPID; 185 else 186 pid_file = NULL; 187 } 188 dotrim(ent->log, pid_file, ent->numlogs, 189 ent->flags, ent->permissions, ent->uid, ent->gid, ent->sig); 190 } else { 191 if (verbose) 192 printf("--> skipping\n"); 193 } 194 } 195 } 196 197 static void PRS(argc,argv) 198 int argc; 199 char **argv; 200 { 201 int c; 202 char *p; 203 204 timenow = time((time_t *) 0); 205 daytime = ctime(&timenow) + 4; 206 daytime[15] = '\0'; 207 208 /* Let's get our hostname */ 209 (void) gethostname(hostname, sizeof(hostname)); 210 211 /* Truncate domain */ 212 if ((p = strchr(hostname, '.'))) { 213 *p = '\0'; 214 } 215 216 optind = 1; /* Start options parsing */ 217 while ((c=getopt(argc,argv,"nrvFf:t:")) != -1) 218 switch (c) { 219 case 'n': 220 noaction++; 221 break; 222 case 'r': 223 needroot = 0; 224 break; 225 case 'v': 226 verbose++; 227 break; 228 case 'f': 229 conf = optarg; 230 break; 231 case 'F': 232 force++; 233 break; 234 default: 235 usage(); 236 } 237 } 238 239 static void usage() 240 { 241 fprintf(stderr, "usage: newsyslog [-Fnrv] [-f config-file]\n"); 242 exit(1); 243 } 244 245 /* Parse a configuration file and return a linked list of all the logs 246 * to process 247 */ 248 static struct conf_entry *parse_file() 249 { 250 FILE *f; 251 char line[BUFSIZ], *parse, *q; 252 char *errline, *group; 253 struct conf_entry *first = NULL; 254 struct conf_entry *working = NULL; 255 struct passwd *pass; 256 struct group *grp; 257 int eol; 258 259 if (strcmp(conf,"-")) 260 f = fopen(conf,"r"); 261 else 262 f = stdin; 263 if (!f) 264 err(1, "%s", conf); 265 while (fgets(line,BUFSIZ,f)) { 266 if ((line[0]== '\n') || (line[0] == '#')) 267 continue; 268 errline = strdup(line); 269 if (!first) { 270 working = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 271 first = working; 272 } else { 273 working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 274 working = working->next; 275 } 276 277 q = parse = missing_field(sob(line),errline); 278 parse = son(line); 279 if (!*parse) 280 errx(1, "malformed line (missing fields):\n%s", errline); 281 *parse = '\0'; 282 working->log = strdup(q); 283 284 q = parse = missing_field(sob(++parse),errline); 285 parse = son(parse); 286 if (!*parse) 287 errx(1, "malformed line (missing fields):\n%s", errline); 288 *parse = '\0'; 289 if ((group = strchr(q, ':')) != NULL || 290 (group = strrchr(q, '.')) != NULL) { 291 *group++ = '\0'; 292 if (*q) { 293 if (!(isnumber(*q))) { 294 if ((pass = getpwnam(q)) == NULL) 295 errx(1, 296 "error in config file; unknown user:\n%s", 297 errline); 298 working->uid = pass->pw_uid; 299 } else 300 working->uid = atoi(q); 301 } else 302 working->uid = NONE; 303 304 q = group; 305 if (*q) { 306 if (!(isnumber(*q))) { 307 if ((grp = getgrnam(q)) == NULL) 308 errx(1, 309 "error in config file; unknown group:\n%s", 310 errline); 311 working->gid = grp->gr_gid; 312 } else 313 working->gid = atoi(q); 314 } else 315 working->gid = NONE; 316 317 q = parse = missing_field(sob(++parse),errline); 318 parse = son(parse); 319 if (!*parse) 320 errx(1, "malformed line (missing fields):\n%s", errline); 321 *parse = '\0'; 322 } 323 else 324 working->uid = working->gid = NONE; 325 326 if (!sscanf(q,"%o",&working->permissions)) 327 errx(1, "error in config file; bad permissions:\n%s", 328 errline); 329 330 q = parse = missing_field(sob(++parse),errline); 331 parse = son(parse); 332 if (!*parse) 333 errx(1, "malformed line (missing fields):\n%s", errline); 334 *parse = '\0'; 335 if (!sscanf(q,"%d",&working->numlogs)) 336 errx(1, "error in config file; bad number:\n%s", 337 errline); 338 339 q = parse = missing_field(sob(++parse),errline); 340 parse = son(parse); 341 if (!*parse) 342 errx(1, "malformed line (missing fields):\n%s", errline); 343 *parse = '\0'; 344 if (isdigit(*q)) 345 working->size = atoi(q); 346 else 347 working->size = -1; 348 349 working->flags = 0; 350 q = parse = missing_field(sob(++parse),errline); 351 parse = son(parse); 352 eol = !*parse; 353 *parse = '\0'; 354 { 355 char *ep; 356 u_long ul; 357 358 ul = strtoul(q, &ep, 10); 359 if (ep == q) 360 working->hours = 0; 361 else if (*ep == '*') 362 working->hours = -1; 363 else if (ul > INT_MAX) 364 errx(1, "interval is too large:\n%s", errline); 365 else 366 working->hours = ul; 367 368 if (*ep != '\0' && *ep != '@' && *ep != '*') 369 errx(1, "malformed interval/at:\n%s", errline); 370 if (*ep == '@') { 371 if ((working->trim_at = parse8601(ep + 1)) 372 == (time_t)-1) 373 errx(1, "malformed at:\n%s", errline); 374 working->flags |= CE_TRIMAT; 375 } 376 } 377 378 if (eol) 379 q = NULL; 380 else { 381 q = parse = sob(++parse); /* Optional field */ 382 parse = son(parse); 383 if (!*parse) 384 eol = 1; 385 *parse = '\0'; 386 } 387 388 while (q && *q && !isspace(*q)) { 389 if ((*q == 'Z') || (*q == 'z')) 390 working->flags |= CE_COMPACT; 391 else if ((*q == 'B') || (*q == 'b')) 392 working->flags |= CE_BINARY; 393 else if (*q != '-') 394 errx(1, "illegal flag in config file -- %c", *q); 395 q++; 396 } 397 398 if (eol) 399 q = NULL; 400 else { 401 q = parse = sob(++parse); /* Optional field */ 402 parse = son(parse); 403 if (!*parse) 404 eol = 1; 405 *parse = '\0'; 406 } 407 408 working->pid_file = NULL; 409 if (q && *q) { 410 if (*q == '/') 411 working->pid_file = strdup(q); 412 else if (isdigit(*q)) 413 goto got_sig; 414 else 415 errx(1, "illegal pid file or signal number in config file:\n%s", errline); 416 } 417 418 if (eol) 419 q = NULL; 420 else { 421 q = parse = sob(++parse); /* Optional field */ 422 *(parse = son(parse)) = '\0'; 423 } 424 425 working->sig = SIGHUP; 426 if (q && *q) { 427 if (isdigit(*q)) { 428 got_sig: 429 working->sig = atoi(q); 430 } else { 431 err_sig: 432 errx(1, "illegal signal number in config file:\n%s", errline); 433 } 434 if (working->sig < 1 || working->sig >= NSIG) 435 goto err_sig; 436 } 437 438 free(errline); 439 } 440 if (working) 441 working->next = (struct conf_entry *) NULL; 442 (void) fclose(f); 443 return(first); 444 } 445 446 static char *missing_field(p,errline) 447 char *p,*errline; 448 { 449 if (!p || !*p) 450 errx(1, "missing field in config file:\n%s", errline); 451 return(p); 452 } 453 454 static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid,sig) 455 char *log; 456 char *pid_file; 457 int numdays; 458 int flags; 459 int perm; 460 int owner_uid; 461 int group_gid; 462 int sig; 463 { 464 char file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1]; 465 char zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1]; 466 int notified, need_notification, fd, _numdays; 467 struct stat st; 468 pid_t pid; 469 470 #ifdef _IBMR2 471 /* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */ 472 /* change it to be owned by uid -1, instead of leaving it as is, as it is */ 473 /* supposed to. */ 474 if (owner_uid == -1) 475 owner_uid = geteuid(); 476 #endif 477 478 /* Remove oldest log */ 479 (void) sprintf(file1,"%s.%d",log,numdays); 480 (void) strcpy(zfile1, file1); 481 (void) strcat(zfile1, COMPRESS_POSTFIX); 482 483 if (noaction) { 484 printf("rm -f %s\n", file1); 485 printf("rm -f %s\n", zfile1); 486 } else { 487 (void) unlink(file1); 488 (void) unlink(zfile1); 489 } 490 491 /* Move down log files */ 492 _numdays = numdays; /* preserve */ 493 while (numdays--) { 494 (void) strcpy(file2,file1); 495 (void) sprintf(file1,"%s.%d",log,numdays); 496 (void) strcpy(zfile1, file1); 497 (void) strcpy(zfile2, file2); 498 if (lstat(file1, &st)) { 499 (void) strcat(zfile1, COMPRESS_POSTFIX); 500 (void) strcat(zfile2, COMPRESS_POSTFIX); 501 if (lstat(zfile1, &st)) continue; 502 } 503 if (noaction) { 504 printf("mv %s %s\n",zfile1,zfile2); 505 printf("chmod %o %s\n", perm, zfile2); 506 printf("chown %d.%d %s\n", 507 owner_uid, group_gid, zfile2); 508 } else { 509 (void) rename(zfile1, zfile2); 510 (void) chmod(zfile2, perm); 511 (void) chown(zfile2, owner_uid, group_gid); 512 } 513 } 514 if (!noaction && !(flags & CE_BINARY)) 515 (void) log_trim(log); /* Report the trimming to the old log */ 516 517 if (!_numdays) { 518 if (noaction) 519 printf("rm %s\n",log); 520 else 521 (void)unlink(log); 522 } 523 else { 524 if (noaction) 525 printf("mv %s to %s\n",log,file1); 526 else 527 (void)rename(log, file1); 528 } 529 530 if (noaction) 531 printf("Start new log..."); 532 else { 533 fd = creat(log,perm); 534 if (fd < 0) 535 err(1, "can't start new log"); 536 if (fchown(fd, owner_uid, group_gid)) 537 err(1, "can't chmod new log file"); 538 (void) close(fd); 539 if (!(flags & CE_BINARY)) 540 if (log_trim(log)) /* Add status message */ 541 err(1, "can't add status message to log"); 542 } 543 if (noaction) 544 printf("chmod %o %s...\n", perm, log); 545 else 546 (void) chmod(log,perm); 547 548 pid = 0; 549 need_notification = notified = 0; 550 if (pid_file != NULL) { 551 need_notification = 1; 552 pid = get_pid(pid_file); 553 } 554 555 if (pid) { 556 if (noaction) { 557 notified = 1; 558 printf("kill -%d %d\n", sig, (int)pid); 559 } else if (kill(pid,sig)) 560 warn("can't notify daemon, pid %d", (int)pid); 561 else { 562 notified = 1; 563 if (verbose) 564 printf("daemon pid %d notified\n", (int)pid); 565 } 566 } 567 568 if ((flags & CE_COMPACT)) { 569 if (need_notification && !notified) 570 warnx("log not compressed because daemon not notified"); 571 else if (noaction) 572 printf("Compress %s.0\n",log); 573 else { 574 if (notified) { 575 if (verbose) 576 printf("small pause to allow daemon to close log\n"); 577 sleep(10); 578 } 579 compress_log(log); 580 } 581 } 582 } 583 584 /* Log the fact that the logs were turned over */ 585 static int log_trim(log) 586 char *log; 587 { 588 FILE *f; 589 if ((f = fopen(log,"a")) == NULL) 590 return(-1); 591 fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n", 592 daytime, hostname, (int)getpid()); 593 if (fclose(f) == EOF) 594 err(1, "log_trim: fclose:"); 595 return(0); 596 } 597 598 /* Fork of /usr/ucb/compress to compress the old log file */ 599 static void compress_log(log) 600 char *log; 601 { 602 pid_t pid; 603 char tmp[MAXPATHLEN+1]; 604 605 (void) sprintf(tmp,"%s.0",log); 606 pid = fork(); 607 if (pid < 0) 608 err(1, "fork"); 609 else if (!pid) { 610 (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, 0); 611 err(1, _PATH_GZIP); 612 } 613 } 614 615 /* Return size in kilobytes of a file */ 616 static int sizefile(file) 617 char *file; 618 { 619 struct stat sb; 620 621 if (stat(file,&sb) < 0) 622 return(-1); 623 return(kbytes(dbtob(sb.st_blocks))); 624 } 625 626 /* Return the age of old log file (file.0) */ 627 static int age_old_log(file) 628 char *file; 629 { 630 struct stat sb; 631 char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1]; 632 633 (void) strcpy(tmp,file); 634 if (stat(strcat(tmp,".0"),&sb) < 0) 635 if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0) 636 return(-1); 637 return( (int) (timenow - sb.st_mtime + 1800) / 3600); 638 } 639 640 static pid_t get_pid(pid_file) 641 char *pid_file; 642 { 643 FILE *f; 644 char line[BUFSIZ]; 645 pid_t pid = 0; 646 647 if ((f = fopen(pid_file,"r")) == NULL) 648 warn("can't open %s pid file to restart a daemon", 649 pid_file); 650 else { 651 if (fgets(line,BUFSIZ,f)) { 652 pid = atol(line); 653 if (pid < MIN_PID || pid > MAX_PID) { 654 warnx("preposterous process number: %d", (int)pid); 655 pid = 0; 656 } 657 } else 658 warn("can't read %s pid file to restart a daemon", 659 pid_file); 660 (void)fclose(f); 661 } 662 return pid; 663 } 664 665 /* Skip Over Blanks */ 666 char *sob(p) 667 register char *p; 668 { 669 while (p && *p && isspace(*p)) 670 p++; 671 return(p); 672 } 673 674 /* Skip Over Non-Blanks */ 675 char *son(p) 676 register char *p; 677 { 678 while (p && *p && !isspace(*p)) 679 p++; 680 return(p); 681 } 682 683 /* 684 * Parse a limited subset of ISO 8601. 685 * The specific format is as follows: 686 * 687 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 688 * 689 * We don't accept a timezone specification; missing fields (including 690 * timezone) are defaulted to the current date but time zero. 691 */ 692 static time_t 693 parse8601(const char *s) 694 { 695 char *t; 696 struct tm tm, *tmp; 697 u_long ul; 698 699 tmp = localtime(&timenow); 700 tm = *tmp; 701 702 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 703 704 ul = strtoul(s, &t, 10); 705 if (*t != '\0' && *t != 'T') 706 return -1; 707 708 /* 709 * Now t points either to the end of the string (if no time 710 * was provided) or to the letter `T' which separates date 711 * and time in ISO 8601. The pointer arithmetic is the same for 712 * either case. 713 */ 714 switch (t - s) { 715 case 8: 716 tm.tm_year = ((ul / 1000000) - 19) * 100; 717 ul = ul % 1000000; 718 case 6: 719 tm.tm_year = tm.tm_year - (tm.tm_year % 100); 720 tm.tm_year += ul / 10000; 721 ul = ul % 10000; 722 case 4: 723 tm.tm_mon = (ul / 100) - 1; 724 ul = ul % 100; 725 case 2: 726 tm.tm_mday = ul; 727 case 0: 728 break; 729 default: 730 return -1; 731 } 732 733 /* sanity check */ 734 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 735 || tm.tm_mday < 1 || tm.tm_mday > 31) 736 return -1; 737 738 if (*t != '\0') { 739 s = ++t; 740 ul = strtoul(s, &t, 10); 741 if (*t != '\0' && !isspace(*t)) 742 return -1; 743 744 switch (t - s) { 745 case 6: 746 tm.tm_sec = ul % 100; 747 ul /= 100; 748 case 4: 749 tm.tm_min = ul % 100; 750 ul /= 100; 751 case 2: 752 tm.tm_hour = ul; 753 case 0: 754 break; 755 default: 756 return -1; 757 } 758 759 /* sanity check */ 760 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 761 || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 762 return -1; 763 } 764 765 return mktime(&tm); 766 } 767 768 769