/*
* 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 <agulbra@troll.no> 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 <socks.h>
#endif
#include <ctype.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#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;
}
syntax highlighted by Code2HTML, v. 0.9.1