1 /* $NetBSD: newsyslog.c,v 1.12 1996/09/27 01:56:56 thorpej Exp $ */ 2 3 /* 4 * This file contains changes from the Open Software Foundation. 5 */ 6 7 /* 8 9 Copyright 1988, 1989 by the Massachusetts Institute of Technology 10 11 Permission to use, copy, modify, and distribute this software 12 and its documentation for any purpose and without fee is 13 hereby granted, provided that the above copyright notice 14 appear in all copies and that both that copyright notice and 15 this permission notice appear in supporting documentation, 16 and that the names of M.I.T. and the M.I.T. S.I.P.B. not be 17 used in advertising or publicity pertaining to distribution 18 of the software without specific, written prior permission. 19 M.I.T. and the M.I.T. S.I.P.B. make no representations about 20 the suitability of this software for any purpose. It is 21 provided "as is" without express or implied warranty. 22 23 */ 24 25 /* 26 * newsyslog - roll over selected logs at the appropriate time, 27 * keeping the a specified number of backup files around. 28 */ 29 30 #ifndef lint 31 static char rcsid[] = "$NetBSD: newsyslog.c,v 1.12 1996/09/27 01:56:56 thorpej Exp $"; 32 #endif /* not lint */ 33 34 #ifndef CONF 35 #define CONF "/etc/athena/newsyslog.conf" /* Configuration file */ 36 #endif 37 #ifndef PIDFILE 38 #define PIDFILE "/etc/syslog.pid" 39 #endif 40 #ifndef COMPRESS 41 #define COMPRESS "/usr/ucb/compress" /* File compression program */ 42 #endif 43 #ifndef COMPRESS_POSTFIX 44 #define COMPRESS_POSTFIX ".Z" 45 #endif 46 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <string.h> 50 #include <ctype.h> 51 #include <signal.h> 52 #include <pwd.h> 53 #include <grp.h> 54 #include <sys/types.h> 55 #include <sys/time.h> 56 #include <sys/stat.h> 57 #include <sys/param.h> 58 #include <sys/wait.h> 59 60 #define kbytes(size) (((size) + 1023) >> 10) 61 #ifdef _IBMR2 62 /* Calculates (db * DEV_BSIZE) */ 63 #define dbtob(db) ((unsigned)(db) << UBSHIFT) 64 #endif 65 66 #define CE_COMPACT 1 /* Compact the achived log files */ 67 #define CE_BINARY 2 /* Logfile is in binary, don't add */ 68 /* status messages */ 69 #define NONE -1 70 71 struct conf_entry { 72 char *log; /* Name of the log */ 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 int permissions; /* File permissions on the log */ 79 int flags; /* Flags (CE_COMPACT & CE_BINARY) */ 80 struct conf_entry *next; /* Linked list pointer */ 81 }; 82 83 extern int optind; 84 extern char *optarg; 85 extern char *malloc(); 86 extern uid_t getuid(),geteuid(); 87 extern time_t time(); 88 89 char *progname; /* contains argv[0] */ 90 int verbose = 0; /* Print out what's going on */ 91 int needroot = 1; /* Root privs are necessary */ 92 int noaction = 0; /* Don't do anything, just show it */ 93 char *conf = CONF; /* Configuration file to use */ 94 time_t timenow; 95 int syslog_pid; /* read in from /etc/syslog.pid */ 96 #define MIN_PID 3 97 #define MAX_PID 65534 98 char hostname[64]; /* hostname */ 99 char *daytime; /* timenow in human readable form */ 100 101 102 struct conf_entry *parse_file(); 103 char *sob(), *son(), *strdup(), *missing_field(); 104 105 main(argc,argv) 106 int argc; 107 char **argv; 108 { 109 struct conf_entry *p, *q; 110 111 PRS(argc,argv); 112 if (needroot && getuid() && geteuid()) { 113 fprintf(stderr,"%s: must have root privs\n",progname); 114 exit(1); 115 } 116 p = q = parse_file(); 117 while (p) { 118 do_entry(p); 119 p=p->next; 120 free((char *) q); 121 q=p; 122 } 123 exit(0); 124 } 125 126 do_entry(ent) 127 struct conf_entry *ent; 128 129 { 130 int size, modtime; 131 132 if (verbose) { 133 if (ent->flags & CE_COMPACT) 134 printf("%s <%dZ>: ",ent->log,ent->numlogs); 135 else 136 printf("%s <%d>: ",ent->log,ent->numlogs); 137 } 138 size = sizefile(ent->log); 139 modtime = age_old_log(ent->log); 140 if (size < 0) { 141 if (verbose) 142 printf("does not exist.\n"); 143 } else { 144 if (verbose && (ent->size > 0)) 145 printf("size (Kb): %d [%d] ", size, ent->size); 146 if (verbose && (ent->hours > 0)) 147 printf(" age (hr): %d [%d] ", modtime, ent->hours); 148 if (((ent->size > 0) && (size >= ent->size)) || 149 ((ent->hours > 0) && ((modtime >= ent->hours) 150 || (modtime < 0)))) { 151 if (verbose) 152 printf("--> trimming log....\n"); 153 if (noaction && !verbose) { 154 if (ent->flags & CE_COMPACT) 155 printf("%s <%dZ>: trimming", 156 ent->log,ent->numlogs); 157 else 158 printf("%s <%d>: trimming", 159 ent->log,ent->numlogs); 160 } 161 dotrim(ent->log, ent->numlogs, ent->flags, 162 ent->permissions, ent->uid, ent->gid); 163 } else { 164 if (verbose) 165 printf("--> skipping\n"); 166 } 167 } 168 } 169 170 PRS(argc,argv) 171 int argc; 172 char **argv; 173 { 174 int c; 175 FILE *f; 176 char line[BUFSIZ]; 177 char *p; 178 179 progname = argv[0]; 180 timenow = time((time_t *) 0); 181 daytime = ctime(&timenow) + 4; 182 daytime[15] = '\0'; 183 184 /* Let's find the pid of syslogd */ 185 syslog_pid = 0; 186 f = fopen(PIDFILE,"r"); 187 if (f && fgets(line,BUFSIZ,f)) 188 syslog_pid = atoi(line); 189 if (f) 190 (void)fclose(f); 191 192 /* Let's get our hostname */ 193 (void) gethostname(hostname, sizeof(hostname)); 194 195 /* Truncate domain */ 196 if (p = strchr(hostname, '.')) { 197 *p = '\0'; 198 } 199 200 optind = 1; /* Start options parsing */ 201 while ((c=getopt(argc,argv,"nrvf:t:")) != EOF) 202 switch (c) { 203 case 'n': 204 noaction++; /* This implies needroot as off */ 205 /* fall through */ 206 case 'r': 207 needroot = 0; 208 break; 209 case 'v': 210 verbose++; 211 break; 212 case 'f': 213 conf = optarg; 214 break; 215 default: 216 usage(); 217 } 218 } 219 220 usage() 221 { 222 fprintf(stderr, 223 "Usage: %s <-nrv> <-f config-file>\n", progname); 224 exit(1); 225 } 226 227 /* Parse a configuration file and return a linked list of all the logs 228 * to process 229 */ 230 struct conf_entry *parse_file() 231 { 232 FILE *f; 233 char line[BUFSIZ], *parse, *q; 234 char *errline, *group; 235 struct conf_entry *first = NULL; 236 struct conf_entry *working; 237 struct passwd *pass; 238 struct group *grp; 239 240 if (strcmp(conf,"-")) 241 f = fopen(conf,"r"); 242 else 243 f = stdin; 244 if (!f) { 245 (void) fprintf(stderr,"%s: ",progname); 246 perror(conf); 247 exit(1); 248 } 249 while (fgets(line,BUFSIZ,f)) { 250 if ((line[0]== '\n') || (line[0] == '#')) 251 continue; 252 errline = strdup(line); 253 if (!first) { 254 working = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 255 first = working; 256 } else { 257 working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 258 working = working->next; 259 } 260 261 q = parse = missing_field(sob(line),errline); 262 *(parse = son(line)) = '\0'; 263 working->log = strdup(q); 264 265 q = parse = missing_field(sob(++parse),errline); 266 *(parse = son(parse)) = '\0'; 267 if ((group = strchr(q, '.')) != NULL) { 268 *group++ = '\0'; 269 if (*q) { 270 if (!(isnumber(q))) { 271 if ((pass = getpwnam(q)) == NULL) { 272 fprintf(stderr, 273 "Error in config file; unknown user:\n"); 274 fputs(errline,stderr); 275 exit(1); 276 } 277 working->uid = pass->pw_uid; 278 } else 279 working->uid = atoi(q); 280 } else 281 working->uid = NONE; 282 283 q = group; 284 if (*q) { 285 if (!(isnumber(q))) { 286 if ((grp = getgrnam(q)) == NULL) { 287 fprintf(stderr, 288 "Error in config file; unknown group:\n"); 289 fputs(errline,stderr); 290 exit(1); 291 } 292 working->gid = grp->gr_gid; 293 } else 294 working->gid = atoi(q); 295 } else 296 working->gid = NONE; 297 298 q = parse = missing_field(sob(++parse),errline); 299 *(parse = son(parse)) = '\0'; 300 } 301 else 302 working->uid = working->gid = NONE; 303 304 if (!sscanf(q,"%o",&working->permissions)) { 305 fprintf(stderr, 306 "Error in config file; bad permissions:\n"); 307 fputs(errline,stderr); 308 exit(1); 309 } 310 311 q = parse = missing_field(sob(++parse),errline); 312 *(parse = son(parse)) = '\0'; 313 if (!sscanf(q,"%d",&working->numlogs)) { 314 fprintf(stderr, 315 "Error in config file; bad number:\n"); 316 fputs(errline,stderr); 317 exit(1); 318 } 319 320 q = parse = missing_field(sob(++parse),errline); 321 *(parse = son(parse)) = '\0'; 322 if (isdigit(*q)) 323 working->size = atoi(q); 324 else 325 working->size = -1; 326 327 q = parse = missing_field(sob(++parse),errline); 328 *(parse = son(parse)) = '\0'; 329 if (isdigit(*q)) 330 working->hours = atoi(q); 331 else 332 working->hours = -1; 333 334 q = parse = sob(++parse); /* Optional field */ 335 *(parse = son(parse)) = '\0'; 336 working->flags = 0; 337 while (q && *q && !isspace(*q)) { 338 if ((*q == 'Z') || (*q == 'z')) 339 working->flags |= CE_COMPACT; 340 else if ((*q == 'B') || (*q == 'b')) 341 working->flags |= CE_BINARY; 342 else { 343 fprintf(stderr, 344 "Illegal flag in config file -- %c\n", 345 *q); 346 exit(1); 347 } 348 q++; 349 } 350 351 free(errline); 352 } 353 if (working) 354 working->next = (struct conf_entry *) NULL; 355 (void) fclose(f); 356 return(first); 357 } 358 359 char *missing_field(p,errline) 360 char *p,*errline; 361 { 362 if (!p || !*p) { 363 fprintf(stderr,"Missing field in config file:\n"); 364 fputs(errline,stderr); 365 exit(1); 366 } 367 return(p); 368 } 369 370 dotrim(log,numdays,flags,perm,owner_uid,group_gid) 371 char *log; 372 int numdays; 373 int flags; 374 int perm; 375 int owner_uid; 376 int group_gid; 377 { 378 char file1[128], file2[128]; 379 char zfile1[128], zfile2[128]; 380 int fd; 381 struct stat st; 382 int ngen = numdays; 383 384 #ifdef _IBMR2 385 /* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */ 386 /* change it to be owned by uid -1, instead of leaving it as is, as it is */ 387 /* supposed to. */ 388 if (owner_uid == -1) 389 owner_uid = geteuid(); 390 #endif 391 392 /* Remove oldest log */ 393 (void) sprintf(file1,"%s.%d",log,numdays); 394 (void) strcpy(zfile1, file1); 395 (void) strcat(zfile1, COMPRESS_POSTFIX); 396 397 if (noaction) { 398 printf("rm -f %s\n", file1); 399 printf("rm -f %s\n", zfile1); 400 } else { 401 (void) unlink(file1); 402 (void) unlink(zfile1); 403 } 404 405 /* Move down log files */ 406 while (numdays--) { 407 (void) strcpy(file2,file1); 408 (void) sprintf(file1,"%s.%d",log,numdays); 409 (void) strcpy(zfile1, file1); 410 (void) strcpy(zfile2, file2); 411 if (lstat(file1, &st)) { 412 (void) strcat(zfile1, COMPRESS_POSTFIX); 413 (void) strcat(zfile2, COMPRESS_POSTFIX); 414 if (lstat(zfile1, &st)) continue; 415 } 416 if (noaction) { 417 printf("mv %s %s\n",zfile1,zfile2); 418 printf("chmod %o %s\n", perm, zfile2); 419 printf("chown %d.%d %s\n", 420 owner_uid, group_gid, zfile2); 421 } else { 422 (void) rename(zfile1, zfile2); 423 (void) chmod(zfile2, perm); 424 (void) chown(zfile2, owner_uid, group_gid); 425 } 426 } 427 if (!noaction && !(flags & CE_BINARY)) 428 (void) log_trim(log); /* Report the trimming to the old log */ 429 430 if (ngen == 0) 431 if (noaction) 432 printf("rm %s\n",log); 433 else 434 (void) unlink(log); 435 else 436 if (noaction) 437 printf("mv %s to %s\n",log,file1); 438 else 439 (void) rename(log,file1); 440 441 if (noaction) 442 printf("Start new log..."); 443 else { 444 fd = creat(log,perm); 445 if (fd < 0) { 446 perror("can't start new log"); 447 exit(1); 448 } 449 if (fchown(fd, owner_uid, group_gid)) { 450 perror("can't chmod new log file"); 451 exit(1); 452 } 453 (void) close(fd); 454 if (!(flags & CE_BINARY)) 455 if (log_trim(log)) { /* Add status message */ 456 perror("can't add status message to log"); 457 exit(1); 458 } 459 } 460 if (noaction) 461 printf("chmod %o %s...",perm,log); 462 else 463 (void) chmod(log,perm); 464 if (noaction) 465 printf("kill -HUP %d (syslogd)\n",syslog_pid); 466 else 467 if (syslog_pid < MIN_PID || syslog_pid > MAX_PID) { 468 fprintf(stderr,"%s: preposterous process number: %d\n", 469 progname, syslog_pid); 470 } else if (kill(syslog_pid,SIGHUP)) { 471 fprintf(stderr,"%s: ",progname); 472 perror("warning - could not restart syslogd"); 473 } 474 if (flags & CE_COMPACT) { 475 if (noaction) 476 printf("Compress %s.0\n",log); 477 else 478 compress_log(log); 479 } 480 } 481 482 /* Log the fact that the logs were turned over */ 483 log_trim(log) 484 char *log; 485 { 486 FILE *f; 487 if ((f = fopen(log,"a")) == NULL) 488 return(-1); 489 fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n", 490 daytime, hostname, getpid()); 491 if (fclose(f) == EOF) { 492 perror("log_trim: fclose:"); 493 exit(1); 494 } 495 return(0); 496 } 497 498 /* Fork of /usr/ucb/compress to compress the old log file */ 499 compress_log(log) 500 char *log; 501 { 502 int pid; 503 char tmp[128]; 504 505 pid = fork(); 506 (void) sprintf(tmp,"%s.0",log); 507 if (pid < 0) { 508 fprintf(stderr,"%s: ",progname); 509 perror("fork"); 510 exit(1); 511 } else if (!pid) { 512 (void) execl(COMPRESS,"compress","-f",tmp,0); 513 fprintf(stderr,"%s: ",progname); 514 perror(COMPRESS); 515 exit(1); 516 } 517 } 518 519 /* Return size in kilobytes of a file */ 520 int sizefile(file) 521 char *file; 522 { 523 struct stat sb; 524 525 if (stat(file,&sb) < 0) 526 return(-1); 527 return(kbytes(dbtob(sb.st_blocks))); 528 } 529 530 /* Return the age of old log file (file.0) */ 531 int age_old_log(file) 532 char *file; 533 { 534 struct stat sb; 535 char tmp[MAXPATHLEN+3]; 536 537 (void) strcpy(tmp,file); 538 if (stat(strcat(tmp,".0"),&sb) < 0) 539 if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0) 540 return(-1); 541 return( (int) (timenow - sb.st_mtime + 1800) / 3600); 542 } 543 544 545 #ifndef OSF 546 /* Duplicate a string using malloc */ 547 548 char *strdup(strp) 549 register char *strp; 550 { 551 register char *cp; 552 553 if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL) 554 abort(); 555 return(strcpy (cp, strp)); 556 } 557 #endif 558 559 /* Skip Over Blanks */ 560 char *sob(p) 561 register char *p; 562 { 563 while (p && *p && isspace(*p)) 564 p++; 565 return(p); 566 } 567 568 /* Skip Over Non-Blanks */ 569 char *son(p) 570 register char *p; 571 { 572 while (p && *p && !isspace(*p)) 573 p++; 574 return(p); 575 } 576 577 578 /* Check if string is actually a number */ 579 580 isnumber(string) 581 char *string; 582 { 583 while (*string != '\0') { 584 if (*string < '0' || *string > '9') return(0); 585 string++; 586 } 587 return(1); 588 } 589