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