/* * This software is Copyright (c) 1986 by Rick Adams. * * Permission is hereby granted to copy, reproduce, redistribute or * otherwise use this software as long as: there is no monetary * profit gained specifically from the use or reproduction or this * software, it is not sold, rented, traded or otherwise marketed, and * this copyright notice is included prominently in any copy * made. * * The author make no claims as to the fitness or correctness of * this software for any use whatsoever, and it is provided as is. * Any use of this software is at the user's own risk. * * expire - expire daemon runs around and nails all articles that * have expired. */ #ifdef SCCSID static char *SccsId = "@(#)expire.c 2.43 3/19/86"; #endif /* SCCSID */ #include "params.h" #include #if defined(BSD4_2) || defined(BSD4_1C) # include #else # include "ndir.h" #endif char *Progname = "expire"; /* used by xerror to identify failing program */ /* Number of array entries to allocate at a time. */ #define SPACE_INCREMENT 1000 struct expdata { char *e_name; long e_min, e_max; time_t e_droptime, e_expiretime; char e_ignorexp; char e_doarchive; char e_doexpire; }; extern int errno; char NARTFILE[BUFLEN], OARTFILE[BUFLEN]; char PAGFILE[BUFLEN], DIRFILE[BUFLEN]; char NACTIVE[BUFLEN], OACTIVE[BUFLEN]; char recdate[BUFLEN]; long rectime, exptime; extern char *OLDNEWS; int verbose = 0; int ignorexp = 0; int doarchive = 0; int nohistory = 0; int dorebuild = 0; int usepost = 0; int frflag = 0; int updateactive = 0; char baduser[BUFLEN]; extern char filename[], nbuf[]; /* * This code uses realloc to get more of the multhist array. */ struct multhist { char *mh_ident; char *mh_file; } *multhist; unsigned int mh_size; char *calloc(); char *realloc(); typedef struct { char *dptr; int dsize; } datum; long expincr; long dropincr; long atol(); time_t cgtdate(), time(); FILE *popen(); struct passwd *pw; struct group *gp; char arpat[LBUFLEN]; int arpatlen = 0; char ngpat[LBUFLEN]; int ngpatlen = 0; char afline[BUFLEN]; char grpsleft[BUFLEN]; struct hbuf h; main(argc, argv) int argc; char **argv; { register char *p1, *p2, *p3; register time_t now, newtime; register FILE *fp = NULL; FILE *ohfd, *nhfd; DIR *ngdirp = NULL; static struct direct *ngdir; char fn[BUFLEN]; int i; #ifndef DBM char *ptr, chr; FILE *subfd[10]; char *histfile(); #endif /* !DBM */ pathinit(); (void) umask(N_UMASK); /* * Try to run as NEWSUSR/NEWSGRP */ if ((pw = getpwnam(NEWSUSR)) == NULL) xerror("Cannot get NEWSUSR pw entry"); uid = pw->pw_uid; if ((gp = getgrnam(NEWSGRP)) == NULL) xerror("Cannot get NEWSGRP gr entry"); gid = gp->gr_gid; (void) setgid(gid); (void) setuid(uid); expincr = DFLTEXP; dropincr = HISTEXP; ngpat[0] = ','; arpat[0] = ','; while (argc > 1) { switch (argv[1][1]) { case 'v': if (isdigit(argv[1][2])) verbose = argv[1][2] - '0'; else if (argc > 2 && argv[2][0] != '-') { argv++; argc--; verbose = atoi(argv[1]); } else verbose = 1; if (verbose < 3) setbuf(stdout, (char *)NULL); break; case 'e': /* Use this as default expiration time */ if (argc > 2 && argv[2][0] != '-') { argv++; argc--; expincr = atol(argv[1]) * DAYS; } else if (isdigit(argv[1][2])) expincr = atol(&argv[1][2]) * DAYS; break; case 'E': /* Use this as default forget time */ if (argc > 2 && argv[2][0] != '-') { argv++; argc--; dropincr = atol(argv[1]) * DAYS; } else if (isdigit(argv[1][2])) dropincr = atol(&argv[1][2]) * DAYS; if (dropincr < expincr) { dropincr = HISTEXP; fprintf(stderr, "History expiration time < article expiration time. Default used.\n"); } break; case 'I': /* Ignore any existing expiration date */ ignorexp = 2; break; case 'i': /* Ignore any existing expiration date */ ignorexp = 1; break; case 'n': if (argc > 2) { argv++; argc--; while (argc > 1 && argv[1][0] != '-') { int argvlen; argvlen = strlen(argv[1]); if (ngpatlen + argvlen + 2 > sizeof (ngpat)) { xerror("Too many groups specified for -n\n"); } if (ngpat[ngpatlen] == '\0') { ngpat[ngpatlen++] = ','; ngpat[ngpatlen] = '\0'; } strcpy(&ngpat[ngpatlen], argv[1]); ngpatlen += argvlen; argv++; argc--; } argv--; argc++; } break; case 'a': /* archive expired articles */ if (access(OLDNEWS,0) < 0){ perror(OLDNEWS); xerror("No archiving possible\n"); } doarchive++; if (argc > 2) { argv++; argc--; while (argc > 1 && argv[1][0] != '-') { int argvlen; argvlen = strlen(argv[1]); if (arpatlen + argvlen + 2 > sizeof (arpat)) { xerror("Too many groups specified for -a\n"); } if (arpat[arpatlen] == '\0') { arpat[arpatlen++] = ','; arpat[arpatlen] = '\0'; } strcpy(&arpat[arpatlen], argv[1]); arpatlen += argvlen; argv++; argc--; } argv--; argc++; } break; case 'h': /* ignore history */ nohistory++; break; case 'r': /* rebuild history file */ dorebuild++; nohistory++; break; case 'p': usepost++; break; case 'f': frflag++; if (argc > 2) { strcpy(baduser, argv[2]); argv++; argc--; } break; case 'u': updateactive++; break; default: printf("Usage: expire [ -v [level] ] [-e days ] [-i] [-a] [-r] [-h] [-p] [-u] [-f username] [-n newsgroups]\n"); xxit(1); } argc--; argv++; } if (ngpat[0] == ',') (void) strcpy(ngpat, "all,"); if (arpat[0] == ',') (void) strcpy(arpat, "all,"); now = time((time_t)0); if (chdir(SPOOL)) xerror("Cannot chdir %s", SPOOL); if (verbose) { printf("expire: nohistory %d, rebuild %d, doarchive %d\n", nohistory, dorebuild, doarchive); printf("newsgroups: %s\n",ngpat); if (doarchive) printf("archiving: %s\n",arpat); } (void) sprintf(OARTFILE, "%s/%s", LIB, "ohistory"); (void) sprintf(NARTFILE, "%s/%s", LIB, "nhistory"); (void) sprintf(OACTIVE, "%s/%s", LIB, "oactive"); (void) sprintf(NACTIVE, "%s/%s", LIB, "nactive"); if (updateactive) goto doupdateactive; #ifdef DBM if (!dorebuild) { (void) sprintf(PAGFILE, "%s/%s", LIB, "nhistory.pag"); (void) sprintf(DIRFILE, "%s/%s", LIB, "nhistory.dir"); (void) close(creat(PAGFILE, 0666)); (void) close(creat(DIRFILE, 0666)); initdbm(NARTFILE); } #endif if (nohistory) { ohfd = xfopen(ACTIVE, "r"); if (dorebuild) { /* Allocate initial space for multiple newsgroup (for an article) array */ multhist = (struct multhist *)calloc (SPACE_INCREMENT, sizeof (struct multhist)); mh_size = SPACE_INCREMENT; (void) sprintf(afline, "exec sort -t\t +1.6 -2 +1 >%s", NARTFILE); if ((nhfd = popen(afline, "w")) == NULL) xerror("Cannot exec %s", afline); } else nhfd = xfopen("/dev/null", "w"); } else { ohfd = xfopen(ARTFILE, "r"); nhfd = xfopen(NARTFILE, "w"); } for(i=0;id_name)); p2 = fn; if (verbose > 2) printf("article: %s\n", fn); strcpy(filename, dirname(fn)); fp = access(filename, 04) ? NULL : fopen(filename, "r"); } else { char dc; if (fgets(afline, BUFLEN, ohfd) == NULL) break; if (verbose > 2) printf("article: %s", afline); p1 = index(afline, '\t'); if (!p1) continue; *p1 = '\0'; (void) strcpy(h.ident, afline); *p1 = '\t'; p2 = index(p1 + 1, '\t'); if (!p2) continue; *p2 = '\0'; (void) strcpy(recdate, p1+1); rectime = cgtdate(recdate); *p2++ = '\t'; (void) strcpy(nbuf, p2); p3 = index(nbuf, '/'); if (p3) { register char *p4; p4 = index(p3, '\n'); if (p4) { while (p4[-1] == ' ') p4--; *p4 = '\0'; } /* * convert list of newsgroups from * ng1/num ng2/num ... * to * ng1,ng2,... */ p4 = p3; do { *p3++ = NGDELIM; while (*p4 != '\0' && *p4 != ' ') p4++; if (*p4++ == '\0') { *--p3 = '\0'; break; } while (*p3 = *p4++) { if (*p3 == '/') break; else p3++; } } while (*p3); } else { /* * Nothing after the 2nd tab. This happens * when there's no message left in the spool * directory, only the memory of it in the * history file. Use date in the history file * to decide if we should keep this article. */ grpsleft[0] = '\0'; goto checkdate; } if (!ngmatch(nbuf, ngpat) || ((rectime+expincr > now) && !dorebuild && !frflag && !usepost && recdate[0] != ' ')) goto keephist; if (recdate[0] != ' ') { grpsleft[0] = '\0'; goto nailit; /* just expire it */ } /* * Look for the file--possibly several times, * if it was posted to several news groups. */ dc = ' '; p3 = p2; while (dc != '\n') { p1 = index(p3, ' '); if (p1) { dc = ' '; *p1 = '\0'; } else { p1 = index(p3, '\n'); if (p1 && p1 > p3) { dc = '\n'; *p1 = '\0'; } else { fp = NULL; break; } } strcpy(filename, dirname(p3)); if (access(filename, 4) == 0 && ((fp=fopen(filename, "r")) != NULL)) break; p3 = p1 + 1; } if (p1) *p1 = dc; } if (fp == NULL) { /* * this probably means that the article has been * cancelled. Lets assume that, and make an * entry in the history file to that effect. */ if (verbose) perror(filename); strcpy(p2, "cancelled\n"); grpsleft[0] = '\0'; goto checkdate; } for(i=0; itm_mon+1, tm->tm_mday, tm->tm_year, tm->tm_hour, tm->tm_min, filename); (void) fclose(fp); continue; } for (mhp = multhist; mhp < multhist+mh_size && mhp->mh_ident != NULL; mhp++) { if (mhp->mh_file == NULL) continue; if (strcmp(mhp->mh_ident, h.ident)) continue; (void) strcat(filename, " "); (void) strcat(filename, mhp->mh_file); free(mhp->mh_file); mhp->mh_file = NULL; /* * if we have all the links, write to hist now */ if (chrcnt(filename, ' ') == chrcnt(cp,NGDELIM)) goto saveit; break; } /* * Here is where we realloc the multhist space rather * than the old way of static allocation. It's * really trivial. We just clear out the space * in case it was reused. The old static array was * guaranteed to be cleared since it was cleared when * the process started. */ if (mhp >= multhist + mh_size) { multhist = (struct multhist *) realloc ((char *)multhist, sizeof (struct multhist) * (SPACE_INCREMENT + mh_size)); if (multhist == NULL) xerror("Too many articles with multiple newsgroups"); for (mhp = multhist + mh_size; mhp < multhist+mh_size+SPACE_INCREMENT; mhp++) { mhp->mh_ident = NULL; mhp->mh_file = NULL; } mhp = multhist + mh_size; mh_size += SPACE_INCREMENT; } if (mhp->mh_ident == NULL) { mhp->mh_ident = malloc(strlen(h.ident)+1); (void) strcpy(mhp->mh_ident, h.ident); } cp = malloc(strlen(filename) + 1); if (cp == NULL) xerror("Out of memory"); (void) strcpy(cp, filename); mhp->mh_file = cp; (void) fclose(fp); continue; } (void) fclose(fp); rectime = cgtdate(recdate); if (h.expdate[0]) exptime = cgtdate(h.expdate); newtime = (usepost ? cgtdate(h.subdate) : rectime) + expincr; if (!h.expdate[0] || ignorexp == 2 || (ignorexp == 1 && newtime < exptime)) exptime = newtime; if (frflag ? strcmp(baduser,h.from)==0 : now >= exptime) { nailit: #ifdef DEBUG printf("cancel %s\n", filename); #else /* !DEBUG */ if (verbose) printf("cancel %s\n", h.ident); ulall(p2, &h); (void) sprintf(p2, "%s\n", grpsleft); if (verbose > 2 && grpsleft[0]) printf("Some good in %s\n", h.ident); #endif /* !DEBUG */ } else { if (verbose > 2) printf("Good article %s\n", h.ident); grpsleft[0] = '!'; } checkdate: if (grpsleft[0] == '\0' && now >= rectime + dropincr) { if (verbose > 3) printf("Drop history of %s - %s\n", h.ident, recdate); } else { long hpos; keephist: hpos = ftell(nhfd); if (verbose > 3) printf("Retain history of %s - %s\n", h.ident, recdate); if (fputs(afline, nhfd) == EOF) xerror("history write failed"); #ifdef DBM if (!dorebuild) remember(h.ident, hpos); #endif /* DBM */ } } out: if (dorebuild) { register struct multhist *mhp; struct tm *tm; for (mhp = multhist; mhp < multhist+mh_size && mhp->mh_ident != NULL; mhp++) if (mhp->mh_file != NULL) { if (verbose) printf("Article: %s [%s] Cannot find all links\n", mhp->mh_ident, mhp->mh_file); (void) sprintf(filename,"%s/%s",SPOOL,mhp->mh_file); for (p1 = filename; *p1 != ' ' && *p1 != '\0'; p1++) if (*p1 == '.') *p1 = '/'; *p1 = '\0'; if ((fp = fopen(filename, "r")) == NULL) { if (verbose) printf("Can't open %s.\n", filename); continue; } if (!hread(&h, fp, TRUE)) { printf("Garbled article %s.\n", filename); (void) fclose(fp); continue; } else { struct stat statb; if (fstat(fileno(fp), &statb) < 0) rectime = cgtdate(h.subdate); else rectime = statb.st_mtime; } tm = localtime(&rectime); if ( #ifdef USG fprintf(nhfd,"%s\t%s%2.2d/%2.2d/%d %2.2d:%2.2d\t%s\n", #else /* !USG */ fprintf(nhfd,"%s\t%s%02d/%02d/%d %02d:%02d\t%s\n", #endif /* !USG */ h.ident, h.expdate[0] ? " " : "", tm->tm_mon+1, tm->tm_mday, tm->tm_year, tm->tm_hour, tm->tm_min, mhp->mh_file) == EOF ) xerror("History write failed"); (void) fclose(fp); continue; } (void) pclose(nhfd); free ((char *)multhist); } else fclose(nhfd); if (dorebuild || !nohistory) { (void) rename(ARTFILE, OARTFILE); (void) rename(NARTFILE, ARTFILE); #ifdef DBM if (dorebuild) rebuilddbm( ); else { char tempname[BUFLEN]; (void) sprintf(tempname,"%s.pag", ARTFILE); (void) strcat(OARTFILE, ".pag"); (void) strcat(NARTFILE, ".pag"); (void) rename(tempname, OARTFILE); (void) rename(NARTFILE, tempname); (void) sprintf(tempname,"%s.dir", ARTFILE); (void) strcpy(rindex(OARTFILE, '.'), ".dir"); (void) strcpy(rindex(NARTFILE, '.'), ".dir"); (void) rename(tempname, OARTFILE); (void) rename(NARTFILE, tempname); } #endif } #ifndef DBM /* rebuild history subfiles */ for (i = 0; i < 10; i++) { (void) sprintf(fn, "%s.d/%c", ARTFILE, i + '0'); close(creat(fn, 0644)); subfd[i] = xfopen(fn, "w+"); } ohfd = xfopen(ARTFILE, "r"); while (fgets(fn, BUFLEN, ohfd) != NULL) { ptr = histfile(fn); chr = *(ptr + strlen(ptr) - 1); if (isdigit(chr)) i = chr - '0'; else i = 0; fputs(fn, subfd[i]); } (void) fclose(ohfd); for (i = 0; i < 10; i++) fclose(subfd[i]); #endif /* !DBM */ doupdateactive: ohfd = xfopen(ACTIVE, "r"); nhfd = xfopen(NACTIVE, "w"); do { long n; long maxart, minart; char cansub; int gdsize, hassubs; struct stat stbuf; if (fgets(afline, BUFLEN, ohfd) == NULL) continue; if (sscanf(afline,"%s %ld %ld %c",nbuf,&maxart, &minart, &cansub) < 4) xerror("Active file corrupt"); if (!ngmatch(nbuf, ngpat)) { if (fputs(afline, nhfd) == EOF) xerror("active file write failed"); continue; } minart = 99999L; /* Change a group name from a.b.c to a/b/c */ for (p1=nbuf; *p1; p1++) if (*p1 == '.') *p1 = '/'; hassubs = stat(nbuf, &stbuf) != 0 || stbuf.st_nlink != 2; gdsize = strlen(nbuf); if ((ngdirp = opendir(nbuf)) != NULL) { while (ngdir = readdir(ngdirp)) { nbuf[gdsize] = '/'; (void) strcpy(&nbuf[gdsize+1], ngdir->d_name); /* We have to do a stat because of micro.6809 */ if (hassubs && (stat(nbuf, &stbuf) < 0 || !(stbuf.st_mode&S_IFREG)) ) continue; n = atol(ngdir->d_name); if (n > 0 && n < minart) minart = n; if (n > 0 && n > maxart) maxart = n; } closedir(ngdirp); } afline[gdsize] = '\0'; if (minart > maxart) minart = maxart; if (fprintf(nhfd,"%s %05ld %05ld %c\n", afline, maxart, minart, cansub) == EOF) xerror("Active file write failed"); } while (!feof(ohfd)); (void) fclose(nhfd); (void) fclose(ohfd); (void) rename(ACTIVE, OACTIVE); (void) rename(NACTIVE, ACTIVE); xxit(0); } /* Unlink (using unwound tail recursion) all the articles in 'artlist'. */ ulall(artlist, hp) char *artlist; struct hbuf *hp; { register char *p, *q; int last = 0; char newname[BUFLEN]; time_t timep[2]; char *fn; grpsleft[0] = '\0'; do { if (verbose > 2) printf("ulall '%s', '%s'\n", artlist, hp->subdate); if (nohistory) { last = 1; } else { while (*artlist == ' ' || *artlist == '\n' || *artlist == ',') artlist++; if (*artlist == '\0') return; p = index(artlist, ' '); if (p == NULL) { last = 1; p = index(artlist, '\n'); } if (p == NULL) { last = 1; fn = dirname(artlist); if (unlink(fn) < 0 && errno != ENOENT) perror(fn); return; } if (p) *p = 0; } strcpy(newname, artlist); q = index(newname,'/'); if (q) { *q++ = NGDELIM; *q = '\0'; } else { q = index(newname, '\0'); if (q == artlist) /* null -> the end */ return; /* should be impossible to get here */ } fn = dirname(artlist); if (ngmatch(newname, ngpat)) { if (doarchive){ if (ngmatch(newname, arpat)) { q = fn + strlen(SPOOL) + 1; (void) sprintf(newname, "%s/%s", OLDNEWS, q); if (verbose) printf("link %s to %s\n", fn, newname); if (link(fn, newname) == -1) { if (mkparents(newname) == 0) if (link(fn, newname) == -1) fcopy(fn, newname); } timep[0] = timep[1] = cgtdate(hp->subdate); (void) utime(newname, timep); } } if (verbose) printf("unlink %s\n", fn); if (unlink(fn) < 0 && errno != ENOENT) perror(fn); } else { if (verbose > 3) printf("retain %s (%s)\n", hp->ident, fn); strcat(grpsleft, artlist); strcat(grpsleft, " "); } artlist = p + 1; } while (!last); } fcopy(fn, newname) char *fn, *newname; { int f1, f2; int r; char buf[BUFSIZ]; f1 = open(fn, 0); if (f1 < 0) return -1; f2 = open(newname, 1); if (f2 < 0) { if (errno == ENOENT) { f2 = creat(newname,0644); if (f2 < 0) { close(f1); return -1; } } else { close(f1); return -1; } } while((r=read(f1, buf, BUFSIZ)) > 0) write(f2, buf, r); (void) close(f1); (void) close(f2); return 0; } /* * Count instances of c in s */ chrcnt(s, c) register char *s; register c; { register n = 0; register cc; while (cc = *s++) if (cc == c) n++; return n; } /* * If any parent directories of this dir don't exist, create them. */ mkparents(fullname) char *fullname; { char buf[200]; register char *p; int rc; (void) strcpy(buf, fullname); p = rindex(buf, '/'); if (p) *p = '\0'; if (access(buf, 0) == 0) return 0; mkparents(buf); if ((rc = mkdir(buf, 0755)) < 0) perror("mkdir failed"); if (verbose) printf("mkdir %s, rc %d\n", buf, rc); return rc; } /* Make sure this file is a legal article. */ islegal(fullname, path, name) register char *fullname; register char *path; register char *name; { struct stat buffer; (void) sprintf(fullname, "%s/%s", path, name); /* make sure the article is numeric. */ while (*name != '\0') if (!isascii(*name) || !isdigit(*name)) return 0; else name++; /* Now make sure we don't have a group like net.micro.432, * which is numeric but not a regular file -- i.e., check * for being a regular file. */ if ((stat(fullname, &buffer) == 0) && ((buffer.st_mode & S_IFMT) == S_IFREG)) { /* Now that we found a legal group in a/b/c/4 notation, switch it to a.b.c/4 notation. */ for (name = fullname; name != NULL && *name != '\0'; name++) if (*name == '/' && name != rindex (name, '/')) *name = '.'; return 1; } return 0; } #ifdef DBM /* * This is taken mostly intact from ../cvt/cvt.hist.c and is used at the * end by the options that make a new history file. * Routine to convert history file to dbm file. The old 3 field * history file is still kept there, because we need it for expire * and for a human readable copy. But we keep a dbm hashed copy * around by message ID so we can answer the yes/no question "have * we already seen this message". The content is the ftell offset * into the real history file when we get the article - you can't * really do much with this because the file gets compacted. */ FILE *fd; char namebuf[BUFSIZ]; char lb[BUFSIZ]; rebuilddbm() { register char *p; long fpos; (void) umask(0); (void) sprintf(namebuf, "%s.dir", ARTFILE); (void) close(creat(namebuf, 0666)); (void) sprintf(namebuf, "%s.pag", ARTFILE); (void) close(creat(namebuf, 0666)); (void) sprintf(namebuf, "%s", ARTFILE); fd = fopen(namebuf, "r"); if (fd == NULL) { perror(namebuf); xxit(2); } initdbm(namebuf); while (fpos=ftell(fd), fgets(lb, BUFSIZ, fd) != NULL) { p = index(lb, '\t'); if (p) *p = 0; remember(lb, fpos); } } remember(article, fileoff) register char *article; long fileoff; { datum lhs, rhs; lcase(article); lhs.dptr = article; lhs.dsize = strlen(article) + 1; rhs.dptr = (char *) &fileoff; rhs.dsize = sizeof fileoff; if (verbose > 5) printf("remember: %s @ %ld\n", article, fileoff); if (store(lhs, rhs) < 0) xerror("dbm store failed"); } #endif /* DBM */ xxit(i) { exit(i); }