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 * $Source: /cvsroot/src/usr.bin/newsyslog/newsyslog.c,v $ 28 * $Author: pk $ 29 */ 30 31 #ifndef lint 32 static char rcsid[] = "$Id: newsyslog.c,v 1.7 1994/02/20 09:54:45 pk Exp $"; 33 #endif /* not lint */ 34 35 #ifndef CONF 36 #define CONF "/etc/athena/newsyslog.conf" /* Configuration file */ 37 #endif 38 #ifndef PIDFILE 39 #define PIDFILE "/etc/syslog.pid" 40 #endif 41 #ifndef COMPRESS 42 #define COMPRESS "/usr/ucb/compress" /* File compression program */ 43 #endif 44 #ifndef COMPRESS_POSTFIX 45 #define COMPRESS_POSTFIX ".Z" 46 #endif 47 48 #include <stdio.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 178 progname = argv[0]; 179 timenow = time((time_t *) 0); 180 daytime = ctime(&timenow); 181 daytime[strlen(daytime)-1] = '\0'; 182 183 /* Let's find the pid of syslogd */ 184 syslog_pid = 0; 185 f = fopen(PIDFILE,"r"); 186 if (f && fgets(line,BUFSIZ,f)) 187 syslog_pid = atoi(line); 188 if (f) 189 (void)fclose(f); 190 191 /* Let's get our hostname */ 192 if (gethostname(hostname, 64)) { 193 perror("gethostname"); 194 (void) strcpy(hostname,"Mystery Host"); 195 } 196 197 optind = 1; /* Start options parsing */ 198 while ((c=getopt(argc,argv,"nrvf:t:")) != EOF) 199 switch (c) { 200 case 'n': 201 noaction++; /* This implies needroot as off */ 202 /* fall through */ 203 case 'r': 204 needroot = 0; 205 break; 206 case 'v': 207 verbose++; 208 break; 209 case 'f': 210 conf = optarg; 211 break; 212 default: 213 usage(); 214 } 215 } 216 217 usage() 218 { 219 fprintf(stderr, 220 "Usage: %s <-nrv> <-f config-file>\n"); 221 exit(1); 222 } 223 224 /* Parse a configuration file and return a linked list of all the logs 225 * to process 226 */ 227 struct conf_entry *parse_file() 228 { 229 FILE *f; 230 char line[BUFSIZ], *parse, *q; 231 char *errline, *group; 232 struct conf_entry *first = NULL; 233 struct conf_entry *working; 234 struct passwd *pass; 235 struct group *grp; 236 237 if (strcmp(conf,"-")) 238 f = fopen(conf,"r"); 239 else 240 f = stdin; 241 if (!f) { 242 (void) fprintf(stderr,"%s: ",progname); 243 perror(conf); 244 exit(1); 245 } 246 while (fgets(line,BUFSIZ,f)) { 247 if ((line[0]== '\n') || (line[0] == '#')) 248 continue; 249 errline = strdup(line); 250 if (!first) { 251 working = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 252 first = working; 253 } else { 254 working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 255 working = working->next; 256 } 257 258 q = parse = missing_field(sob(line),errline); 259 *(parse = son(line)) = '\0'; 260 working->log = strdup(q); 261 262 q = parse = missing_field(sob(++parse),errline); 263 *(parse = son(parse)) = '\0'; 264 if ((group = strchr(q, '.')) != NULL) { 265 *group++ = '\0'; 266 if (*q) { 267 if (!(isnumber(q))) { 268 if ((pass = getpwnam(q)) == NULL) { 269 fprintf(stderr, 270 "Error in config file; unknown user:\n"); 271 fputs(errline,stderr); 272 exit(1); 273 } 274 working->uid = pass->pw_uid; 275 } else 276 working->uid = atoi(q); 277 } else 278 working->uid = NONE; 279 280 q = group; 281 if (*q) { 282 if (!(isnumber(q))) { 283 if ((grp = getgrnam(q)) == NULL) { 284 fprintf(stderr, 285 "Error in config file; unknown group:\n"); 286 fputs(errline,stderr); 287 exit(1); 288 } 289 working->gid = grp->gr_gid; 290 } else 291 working->gid = atoi(q); 292 } else 293 working->gid = NONE; 294 295 q = parse = missing_field(sob(++parse),errline); 296 *(parse = son(parse)) = '\0'; 297 } 298 else 299 working->uid = working->gid = NONE; 300 301 if (!sscanf(q,"%o",&working->permissions)) { 302 fprintf(stderr, 303 "Error in config file; bad permissions:\n"); 304 fputs(errline,stderr); 305 exit(1); 306 } 307 308 q = parse = missing_field(sob(++parse),errline); 309 *(parse = son(parse)) = '\0'; 310 if (!sscanf(q,"%d",&working->numlogs)) { 311 fprintf(stderr, 312 "Error in config file; bad number:\n"); 313 fputs(errline,stderr); 314 exit(1); 315 } 316 317 q = parse = missing_field(sob(++parse),errline); 318 *(parse = son(parse)) = '\0'; 319 if (isdigit(*q)) 320 working->size = atoi(q); 321 else 322 working->size = -1; 323 324 q = parse = missing_field(sob(++parse),errline); 325 *(parse = son(parse)) = '\0'; 326 if (isdigit(*q)) 327 working->hours = atoi(q); 328 else 329 working->hours = -1; 330 331 q = parse = sob(++parse); /* Optional field */ 332 *(parse = son(parse)) = '\0'; 333 working->flags = 0; 334 while (q && *q && !isspace(*q)) { 335 if ((*q == 'Z') || (*q == 'z')) 336 working->flags |= CE_COMPACT; 337 else if ((*q == 'B') || (*q == 'b')) 338 working->flags |= CE_BINARY; 339 else { 340 fprintf(stderr, 341 "Illegal flag in config file -- %c\n", 342 *q); 343 exit(1); 344 } 345 q++; 346 } 347 348 free(errline); 349 } 350 if (working) 351 working->next = (struct conf_entry *) NULL; 352 (void) fclose(f); 353 return(first); 354 } 355 356 char *missing_field(p,errline) 357 char *p,*errline; 358 { 359 if (!p || !*p) { 360 fprintf(stderr,"Missing field in config file:\n"); 361 fputs(errline,stderr); 362 exit(1); 363 } 364 return(p); 365 } 366 367 dotrim(log,numdays,flags,perm,owner_uid,group_gid) 368 char *log; 369 int numdays; 370 int flags; 371 int perm; 372 int owner_uid; 373 int group_gid; 374 { 375 char file1[128], file2[128]; 376 char zfile1[128], zfile2[128]; 377 int fd; 378 struct stat st; 379 380 #ifdef _IBMR2 381 /* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */ 382 /* change it to be owned by uid -1, instead of leaving it as is, as it is */ 383 /* supposed to. */ 384 if (owner_uid == -1) 385 owner_uid = geteuid(); 386 #endif 387 388 /* Remove oldest log */ 389 (void) sprintf(file1,"%s.%d",log,numdays); 390 (void) strcpy(zfile1, file1); 391 (void) strcat(zfile1, COMPRESS_POSTFIX); 392 393 if (noaction) { 394 printf("rm -f %s\n", file1); 395 printf("rm -f %s\n", zfile1); 396 } else { 397 (void) unlink(file1); 398 (void) unlink(zfile1); 399 } 400 401 /* Move down log files */ 402 while (numdays--) { 403 (void) strcpy(file2,file1); 404 (void) sprintf(file1,"%s.%d",log,numdays); 405 (void) strcpy(zfile1, file1); 406 (void) strcpy(zfile2, file2); 407 if (lstat(file1, &st)) { 408 (void) strcat(zfile1, COMPRESS_POSTFIX); 409 (void) strcat(zfile2, COMPRESS_POSTFIX); 410 if (lstat(zfile1, &st)) continue; 411 } 412 if (noaction) { 413 printf("mv %s %s\n",zfile1,zfile2); 414 printf("chmod %o %s\n", perm, zfile2); 415 printf("chown %d.%d %s\n", 416 owner_uid, group_gid, zfile2); 417 } else { 418 (void) rename(zfile1, zfile2); 419 (void) chmod(zfile2, perm); 420 (void) chown(zfile2, owner_uid, group_gid); 421 } 422 } 423 if (!noaction && !(flags & CE_BINARY)) 424 (void) log_trim(log); /* Report the trimming to the old log */ 425 426 if (noaction) 427 printf("mv %s to %s\n",log,file1); 428 else 429 (void) rename(log,file1); 430 if (noaction) 431 printf("Start new log..."); 432 else { 433 fd = creat(log,perm); 434 if (fd < 0) { 435 perror("can't start new log"); 436 exit(1); 437 } 438 if (fchown(fd, owner_uid, group_gid)) { 439 perror("can't chmod new log file"); 440 exit(1); 441 } 442 (void) close(fd); 443 if (!(flags & CE_BINARY)) 444 if (log_trim(log)) { /* Add status message */ 445 perror("can't add status message to log"); 446 exit(1); 447 } 448 } 449 if (noaction) 450 printf("chmod %o %s...",perm,log); 451 else 452 (void) chmod(log,perm); 453 if (noaction) 454 printf("kill -HUP %d (syslogd)\n",syslog_pid); 455 else 456 if (syslog_pid < MIN_PID || syslog_pid > MAX_PID) { 457 fprintf(stderr,"%s: preposterous process number: %d\n", 458 progname, syslog_pid); 459 } else if (kill(syslog_pid,SIGHUP)) { 460 fprintf(stderr,"%s: ",progname); 461 perror("warning - could not restart syslogd"); 462 } 463 if (flags & CE_COMPACT) { 464 if (noaction) 465 printf("Compress %s.0\n",log); 466 else 467 compress_log(log); 468 } 469 } 470 471 /* Log the fact that the logs were turned over */ 472 log_trim(log) 473 char *log; 474 { 475 FILE *f; 476 if ((f = fopen(log,"a")) == NULL) 477 return(-1); 478 fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n", 479 daytime, hostname, getpid()); 480 if (fclose(f) == EOF) { 481 perror("log_trim: fclose:"); 482 exit(1); 483 } 484 return(0); 485 } 486 487 /* Fork of /usr/ucb/compress to compress the old log file */ 488 compress_log(log) 489 char *log; 490 { 491 int pid; 492 char tmp[128]; 493 494 pid = fork(); 495 (void) sprintf(tmp,"%s.0",log); 496 if (pid < 0) { 497 fprintf(stderr,"%s: ",progname); 498 perror("fork"); 499 exit(1); 500 } else if (!pid) { 501 (void) execl(COMPRESS,"compress","-f",tmp,0); 502 fprintf(stderr,"%s: ",progname); 503 perror(COMPRESS); 504 exit(1); 505 } 506 } 507 508 /* Return size in kilobytes of a file */ 509 int sizefile(file) 510 char *file; 511 { 512 struct stat sb; 513 514 if (stat(file,&sb) < 0) 515 return(-1); 516 return(kbytes(dbtob(sb.st_blocks))); 517 } 518 519 /* Return the age of old log file (file.0) */ 520 int age_old_log(file) 521 char *file; 522 { 523 struct stat sb; 524 char tmp[MAXPATHLEN+3]; 525 526 (void) strcpy(tmp,file); 527 if (stat(strcat(tmp,".0"),&sb) < 0) 528 if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0) 529 return(-1); 530 return( (int) (timenow - sb.st_mtime + 1800) / 3600); 531 } 532 533 534 #ifndef OSF 535 /* Duplicate a string using malloc */ 536 537 char *strdup(strp) 538 register char *strp; 539 { 540 register char *cp; 541 542 if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL) 543 abort(); 544 return(strcpy (cp, strp)); 545 } 546 #endif 547 548 /* Skip Over Blanks */ 549 char *sob(p) 550 register char *p; 551 { 552 while (p && *p && isspace(*p)) 553 p++; 554 return(p); 555 } 556 557 /* Skip Over Non-Blanks */ 558 char *son(p) 559 register char *p; 560 { 561 while (p && *p && !isspace(*p)) 562 p++; 563 return(p); 564 } 565 566 567 /* Check if string is actually a number */ 568 569 isnumber(string) 570 char *string; 571 { 572 while (*string != '\0') { 573 if (*string < '0' || *string > '9') return(0); 574 string++; 575 } 576 return(1); 577 } 578