/* * Copyright (c) 1998,1999,2000 Kazushi (Jam) Marukawa * All rights of my changes are reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice in the documentation and/or other materials provided with * the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* $Orig-Id: texpire.c,v 1.14 1997/07/20 00:37:18 agulbra Exp $ Written by Arnt Gulbrandsen and copyright 1995 Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47 22646949. Use, modification and distribution is allowed without limitation, warranty, or liability of any kind. */ #ifdef SOCKS #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "leafnode.h" time_t now; unsigned long default_expire; struct exp { char* xover; int kill; int exists; }; /* 05/27/97 - T. Sweeney - Find a group in the expireinfo linked list and return its expire time. Otherwise, return zero. */ unsigned long lookup_expire(char* group) { int i; for (i = 0; i < groupexpires.num; i++) { struct groupexpire* p = &groupexpires.array[i]; if (myfnmatch(p->group, group) == 0) return p->xtime; } return 0; } static void dogroup(struct newsgroup* g) { char gdir[PATH_MAX]; char* p; char* q; DIR* d; struct dirent* de; struct stat st; unsigned long first, last, art; struct exp* articles; int n; int fd; char* overview; /* xover: read then free */ int deleted,kept; deleted = kept = 0; clearidtree(); /* eliminate empty groups */ if (!chdirgroup(g, 0)) return; getcwd(gdir, PATH_MAX); /* find low-water and high-water marks */ d = opendir("."); if (!d) { mysyslog(LOG_ERR, "opendir in %s: %s", gdir, strerror(errno)); return; } first = ULONG_MAX; last = 0; while ((de = readdir(d)) != 0) { art = strtoul(de->d_name, &p, 10); if (p && *p == '\0') { /* * Need to check stat of each entry because it might be * number named directory like alt.260. * * To reduce the number of stat syscall, do little trick here. */ if ((art < first || art > last) && stat(de->d_name, &st) == 0 && (S_ISREG(st.st_mode))) { if (art < first) first = art; if (art > last) last = art; } } } if (last < first) { closedir(d); return; } rewinddir(d); /* allocate and clear article array */ articles = (struct exp*)critmalloc((last - first + 1) * sizeof(struct exp), "Reading articles to expire"); for (art = 0; art <= last - first; art++) { articles[art].xover = NULL; articles[art].kill = 0; articles[art].exists = 0; } /* read in overview info, to be purged and written back */ overview = NULL; if (stat(".overview", &st) == 0) { /* could use mmap() here but I don't think it would help */ overview = critmalloc(st.st_size + 1, "Reading article overview info"); if ((fd = open(".overview", O_RDONLY)) < 0 || (read(fd, overview, st.st_size) < st.st_size)) { mysyslog(LOG_ERR, "can't open/read %s/.overview: %s", gdir, strerror(errno)); *overview = '\0'; if (fd > -1) close(fd); } else { close(fd); overview[st.st_size] = '\0'; /* 0-terminate string */ } p = overview; while (p && *p) { while (p && isspace(*p)) p++; art = strtoul(p, NULL, 10); if (art >= first && art <= last && !articles[art - first].xover) { articles[art - first].xover = p; articles[art - first].kill = 1; } p = strchr(p, '\n'); if (p) { *p = '\0'; if (p[-1] == '\r') p[-1] = '\0'; p++; } } } /* check the syntax of the .overview info, and delete all illegal stuff */ for (art = first; art <= last; art++) { if (articles[art - first].xover && !legalxoverline(articles[art - first].xover)) { mysyslog(LOG_INFO, "bad overview line for %s/%lu", g->name, art); articles[art - first].xover = NULL; } } /* insert articles in tree, and clear 'kill' for new or read articles */ while ((de = readdir(d)) != 0) { art = strtoul(de->d_name, &p, 10); if (p && *p == '\0' && stat(de->d_name, &st) == 0 && (S_ISREG(st.st_mode))) { articles[art - first].kill = 1; articles[art - first].exists = 1; if ((expire > 0) && (st.st_mtime > expire)) { articles[art - first].kill = 0; p = articles[art - first].xover; for (n = 0; n < 4; n++) if (p && (p = strchr(p + 1, '\t'))) p++; q = p ? strchr(p, '\t') : NULL; if (p && q) { *q = '\0'; if (findmsgid(p)) { /* another file with same msgid? */ articles[art - first].kill = 1; } else { insertmsgid(p, art); if (st.st_nlink < 2) { /* repair fs damage */ if (link(de->d_name, getmsgidfname(p))) { if (errno == EEXIST) /* exists, but points to another file */ articles[art-first].kill = 1; else mysyslog(LOG_ERR, "relink of %s failed: " "%s (%s)", p, strerror(errno), getmsgidfname(p)); } else mysyslog(LOG_INFO, "relinked message %s", p); } *q = '\t'; } } else if (articles[art-first].xover) { /* news database inconsistency: delete and be rid of it */ articles[art-first].kill = 1; } else { /* possibly read the xover line into memory? */ } } } } closedir(d); /* compute new low-water mark */ art = first; while (art<=last && articles[art-first].kill) art++; g->first = art; /* remove old postings */ for (art = first; art <= last; art++) { char name[20]; if (articles[art-first].exists) { if (articles[art-first].kill) { sprintf(name, "%lu", art); #if TEST_TEXPIRE printf("testing about texpire: will unlink %s/%ld\n", gdir, art); deleted++; #else if (!unlink(name)) { deleted++; } else if (errno != ENOENT && errno != EEXIST) { kept++; mysyslog(LOG_ERR, "unlink %s/%ld: %s", gdir, art, strerror(errno)); } #endif } else { kept++; } } } free((char*)articles); free(overview); if (last > g->last) /* try to correct insane newsgroup info */ g->last = last; if (deleted || kept) printf("%s: %d articles deleted, %d kept\n", g->name, deleted, kept); if (kept == 0) (void)unlink(".overview"); if (kept == 0 && !chdir("..") && !rmdir(gdir)) printf("removed unnecessary directory %s\n", gdir); } static void expiregroup(struct newsgroup* g) { if (g) { expiregroup(g->right); expire = lookup_expire(g->name); if (!expire) { const char* s = getinterestingngfname(g); struct stat st; expire = default_expire; /* use short expire if the group isn't even possibly interesting */ if (stat(s, &st) < 0) { expire = time(NULL) - (time_t)(timeout_long * 86400); } } dogroup(g); expiregroup(g->left); } } static void expiremsgid(void) { int n; DIR* d; struct dirent* de; struct stat st; int deleted, kept; deleted = kept = 0; initconfig(); for (n = 0; n < 1000; n++) { const char* s = getmsgiddname(n); if (chdir(s)) { if (errno == ENOENT) mkdir(s, 0755); /* file system damage again */ if (chdir(s)) { mysyslog(LOG_ERR, "chdir %s: %s", s, strerror(errno)); continue; } } d = opendir("."); if (!d) continue; while ((de=readdir(d))) { if (stat(de->d_name, &st) == 0) { if (st.st_nlink < 2 && !unlink(de->d_name)) deleted++; else kept++; } } closedir(d); } if (kept || deleted) printf("total: %d articles deleted, %d kept\n", deleted, kept); } int main(int argc, char** argv) { struct passwd* pw; pw = getpwnam("news"); if (!pw) { fprintf(stderr, "no such user: news\n"); exit(1); } setgid(pw->pw_gid); setreuid(pw->pw_uid, pw->pw_uid); if (getuid() != pw->pw_uid || getgid() != pw->pw_gid) { fprintf(stderr, "%s: must be run as news or root\n", argv[0]); exit(1); } expire = 0; lockactive(); readactive(); readconfig(); default_expire = expire; if (expire == 0) { fprintf(stderr, "%s: no expire time\n", argv[0]); exit(2); } openlog("expire", LOG_PID|LOG_CONS, LOG_NEWS); now = time(NULL); expiregroup(active); writeactive(); expiremsgid(); return 0; }