1 /*
2   fastcancel.c - locally cancel articles
3   written by Olaf Titz, July 1997 as part of c-nocem.
4   Public domain.
5 
6   This program takes on stdin a list of Message-IDs and does local
7   cancels on them. I.e. if the article is in the history, it is
8   deleted; if not, it gets recorded in the history.
9   A log entry will be put on stdout for every ID processed.
10 
11   This program manages the history database directly. For C News, call
12   it with LOCK set. For INN, call it with the server paused.
13 
14   Arguments:
15   -d            Debug. Don't do anything, just print what to do.
16   -i            Don't set dbzincore (default: set).
17   -f logformat  Use format string for logging. In this string, a '+'
18                 sign is replaced with '-' for deleted articles and a
19 		'#' sign is replaced with the Message-ID.
20 		Empty string = don't log
21   -h histfile   Use the specified history file.
22   -s spooldir   Use the specified spool directory.
23   -l            Log statistics.
24   -c	 	Generate a canonical cancel Message-ID too.
25 		(Thanks to Wolfgang Zenker for the suggestion.)
26   -r		Only remove articles, don't add anything.
27   -a num        Write file names for fastrm to given descriptor
28                 instead of removing articles.
29 
30 */
31 static char *RCSID="$Id: fastcancel.c,v 1.12 2001/02/16 21:59:39 olaf Exp $";
32 
33 #ifdef INN2
34 #include "cncdbz.h"
35 #else
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <syslog.h>
39 #include <time.h>
40 #include <unistd.h>
41 /* mystring.h conflicts with string.h */
42 #include <string.h>
43 #include "dbz.h"
44 #endif
45 
46 #ifdef DBZ_REVERTED
47 /* INN 2.3 changed the rules _again_ and now uses the same API for standard
48    dbz as for tagged hash. */
49 #define DO_TAGGED_HASH
50 #endif
51 
52 #define MAXLINE 10000
53 #ifndef LOGLEVEL
54 #define LOGLEVEL LOG_NOTICE
55 #endif
56 
57 static char logform0[]="+ #";		/* logging format */
58 static char *logform=logform0,
59     *logmark=logform0,
60     *logform2="";
61 
62 static FILE *hf;              		/* history file */
63 static int debug=0, cancelid=0, rmonly=0;
64 static int sdela=0, sdelf=0, sadda=0;	/* statistics */
65 static int adesc=-1;                    /* fastrm file descriptor */
66 
67 /* where to print history lines */
68 #define HF (debug ? stdout : hf)
69 
usage(const char * p)70 static void usage(const char *p)
71 {
72     fprintf(stderr,
73 	    "usage: %s [-d] [-i] [-l] [-c] [-r] [-f logformat] [-h histfile] [-s spooldir] [-a descriptor]\n",
74 	    p);
75     exit(1);
76     /* NOTREACHED */
77 }
78 
79 /* Put out a log entry. */
logemit(char m,const char * mid)80 static void logemit(char m, const char *mid)
81 {
82     if (!*logform)
83 	return;
84     *logmark=m;
85     printf("%s%s%s\n", logform, mid, logform2);
86 }
87 
88 /* Process a new (not in history) Message-ID. */
do_new(const char * mid,HASH hash)89 static void do_new(const char *mid
90 #ifdef INN2
91                    , HASH hash
92 #endif
93     )
94 {
95 #ifdef INN2
96 #ifndef DO_TAGGED_HASH
97     void *iv;
98     idxrec ov;
99 #ifdef HAVE_INNCONF_EXTENDEDDBZ
100     idxrecext ev;
101 #endif
102 #endif
103 #else
104     datum k, v;
105 #endif
106     long p=-1; /* only used in if(!debug) branches */
107     time_t t=time(0);
108 
109     if (rmonly)
110 	return;
111 
112     if (!debug) {
113 	/* prepare to store in dbz */
114 	if (fseek(hf, 0, SEEK_END)<0) {
115 	    perror("do_new: fseek");
116 	    return;
117 	}
118 	if ((p=ftell(hf))<0) {
119 	    perror("do_new: ftell");
120 	    return;
121 	}
122     }
123 
124     /* generate a history entry */
125 #ifdef INN2
126 #if defined(HAVE_INNCONF_STOREMSGID) && defined(HAVE_INNCONF_STORAGEAPI)
127     if (innconf->storageapi || !innconf->storemsgid)
128         fprintf(HF, "[%s]\t%lu~-~%lu\t\n", HashToText(hash), t, t);
129     else
130         fprintf(HF, "%s\t%lu~-~%lu\t\n", mid, t, t);
131 #else
132     /* old INN 2.0, new INN 2.3 */
133     fprintf(HF, "[%s]\t%lu~-~%lu\t\n", HashToText(hash), t, t);
134 #endif
135 #else
136     /* classic */
137     fprintf(HF, "%s\t%lu~-\n", mid, t);
138 #endif
139 
140     if (!debug) {
141         if (fflush(hf)) {
142             perror("fflush");
143             return;
144         }
145 
146 #ifdef INN2
147 #ifdef DO_TAGGED_HASH
148         /* store in dbz - INN2, tagged hash */
149         if (dbzstore(hash, p) == DBZSTORE_ERROR)
150             perror("dbzstore");
151 #else
152         /* store in dbz - INN2, no tagged hash */
153 #ifdef HAVE_INNCONF_EXTENDEDDBZ
154         if (innconf->extendeddbz) {
155             ev.offset[HISTOFFSET]=p;
156             ev.offset[OVEROFFSET]=0;
157             ev.overindex=OVER_NONE;
158             ev.overlen=0;
159             iv=&ev;
160         } else
161 #endif
162         {
163             ov.offset=p;
164             iv=&ov;
165         }
166         if (dbzstore(hash, iv) == DBZSTORE_ERROR)
167             perror("dbzstore");
168 #endif
169 #else
170         /* store in dbz - classical */
171 	k.dptr=(char *)mid;
172 	k.dsize=strlen(mid)+1;
173 	v.dptr=(char *)&p;
174 	v.dsize=sizeof(p);
175 	if (dbzstore(k, v)<0)
176 	    perror("do_new: dbzstore");
177 #endif
178     }
179     logemit('+', mid);
180     ++sadda;
181 }
182 
183 /* Process an old history line. */
do_old(char * lin,const char * mid)184 static void do_old(char *lin, const char *mid)
185 {
186     char *p, *q;
187 
188     /* skip ID and date fields */
189     (void) strtok(lin, "\t\n");
190     (void) strtok(NULL, "\t\n");
191     p=strtok(NULL, "\t \n");
192 
193     /* get at the file names */
194 #ifdef INN2
195     if (p
196 #ifdef HAVE_INNCONF_STORAGEAPI
197         && innconf->storageapi
198 #endif
199         ) {
200         if (debug)
201             printf("remove %s\n", p);
202         else
203             if (adesc>=0) {
204                 (void) write(adesc, p, strlen(p));
205                 (void) write(adesc, "\n", 1);
206             } else {
207                 (void) SMcancel(TextToToken(p));
208             }
209         logemit('-', mid);
210         ++sdela;
211         return;
212     }
213 #endif
214     while (p) {
215 	for (q=p; *q; ++q)
216 	    if (*q=='.')
217 		*q='/';
218 	if (debug)
219 	    printf("unlink %s\n", p);
220 	else
221 	    if (adesc>=0) {
222 		(void) write(adesc, p, strlen(p));
223 		(void) write(adesc, "\n", 1);
224 	    } else {
225 		(void) unlink(p);
226 	    }
227 	++sdelf;
228 	p=strtok(NULL, "\t \n");
229     }
230     logemit('-', mid);
231     ++sdela;
232 }
233 
234 #ifdef INN2
235 #ifdef HAVE_DBZFETCH_2
236 #define DBZfetch(k,v) dbzfetch(k,v)
237 #else
DBZfetch(const HASH key,OFFSET_T * value)238 static BOOL DBZfetch(const HASH key, OFFSET_T *value)
239 {
240     OFFSET_T val = dbzfetch(key);
241     if (val==(OFFSET_T)-1)
242         return FALSE;
243     *value=val;
244     return TRUE;
245 }
246 #endif
247 #endif
248 
249 /* Process one Message-ID. */
do_one(const char * mid,int old)250 static void do_one(const char *mid, int old)
251 {
252 #ifdef INN2
253     HASH k;
254 #ifndef DO_TAGGED_HASH
255     idxrec ov;
256 #ifdef HAVE_INNCONF_EXTENDEDDBZ
257     idxrecext ev;
258 #endif
259 #endif
260 #else
261     datum k, v;
262 #endif
263     long p;
264     char lbuf[MAXLINE];
265 
266 #ifdef INN2
267     /* look it up in history: INN2 */
268     if (*mid=='[')
269         k=TextToHash(mid+1);
270     else
271         k=HashMessageID(mid);
272 #ifdef DO_TAGGED_HASH
273     if (!DBZfetch(k, &p)) {
274         do_new(mid, k);
275         return;
276     }
277 #else
278     /* look it up in history: INN2, no tagged hash  */
279 #ifdef HAVE_INNCONF_EXTENDEDDBZ
280     if (innconf->extendeddbz) {
281 	if (!DBZfetch(k, &ev)) {
282             do_new(mid, k);
283             return;
284 	}
285 	p=ev.offset[HISTOFFSET];
286     } else
287 #endif
288     {
289 	if (!DBZfetch(k, &ov)) {
290             do_new(mid, k);
291             return;
292 	}
293 	p=ov.offset;
294     }
295 #endif
296 #else
297     /* look it up in history: classic dbz */
298     k.dptr=(char *)mid;
299     k.dsize=strlen(mid)+1;
300     v=dbzfetch(k);
301     if (!v.dptr) {
302 	do_new(mid);
303         return;
304     }
305     if (v.dsize!=sizeof(p)) {
306         /* fatal error */
307         fprintf(stderr, "dbz: invalid dsize %d", v.dsize);
308         return;
309     }
310     p=*((long *)v.dptr);
311 #endif
312     if (!old)
313         /* if the "cancel" ID is already there, do nothing */
314         return;
315     /* retrieve the actual history line */
316     if (fseek(hf, p, SEEK_SET)<0) {
317         perror("do_one: fseek");
318         return;
319     }
320     if (!fgets(lbuf, sizeof(lbuf), hf)) {
321         perror("do_one: fgets");
322     }
323     if (lbuf[strlen(lbuf)-1]!='\n')
324         fprintf(stderr, "warning: %s long history line\n", mid);
325     do_old(lbuf, mid);
326 }
327 
328 extern char *optarg;
329 
main(int argc,char * argv[])330 int main(int argc, char *argv[])
331 {
332     char buf[MAXLINE]="<cancel.";
333     #define BUFO 7
334     int c0, c1=1, i=1, l=0;
335     char *h=HISTFILE;
336     char *p=NEWSARTS;
337 
338 #ifdef INN2
339     if (ReadInnConf() < 0) {
340         perror("main: ReadInnConf");
341         exit(1);
342     }
343 #endif
344     while ((c0=getopt(argc, argv, "dilcrf:h:s:a:"))!=EOF)
345 	switch(c0) {
346 	case 'd': debug=1; break;
347 	case 'i': i=0; break;
348 	case 'l': l=1; break;
349 	case 'c': cancelid=1; break;
350 	case 'f': logform=optarg; break;
351 	case 'h': h=optarg; break;
352 	case 's': p=optarg; break;
353 	case 'r': rmonly=1; break;
354 	case 'a': adesc=atoi(optarg); break;
355 	default: usage(argv[0]);
356 	}
357     if (chdir(p)<0) {
358 	perror("main: chdir");
359 	exit(1);
360     }
361     hf=fopen(h, debug ? "r" : "r+");
362     if (!hf) {
363 	perror("main: fopen");
364 	exit(1);
365     }
366 
367 #ifdef INN2
368 #define dbminit dbzinit
369     if (
370 #ifdef HAVE_INNCONF_STORAGEAPI
371         innconf->storageapi &&
372 #endif
373         adesc<0 && !debug) {
374         BOOL v=TRUE;
375         if (!SMsetup(SM_RDWR, &v)) {
376             perror("main: SMsetup");
377             exit(1);
378         }
379         if (!SMinit()) {
380             perror("main: SMinit");
381             exit(1);
382         }
383     }
384 #else
385     dbzincore(i);
386 #endif
387     if (dbminit(h)<0) {
388 	perror("main: dbmopen");
389 	exit(1);
390     }
391 
392     /* parse the lame attempt at a format string */
393     for (p=logform; *p; ++p)
394 	switch(*p) {
395 	case '+': logmark=p; break;
396 	case '#': *p='\0'; logform2=p+1; break;
397 	}
398 
399     while (fgets(buf+BUFO, sizeof(buf)-BUFO, stdin)) {
400 	c0=c1;
401 	c1=((buf+BUFO)[strlen(buf+BUFO)-1]=='\n');
402 	if (!c0)
403 	    continue; /* skip continuation bogosities */
404 	if (!c1) {
405 	    fprintf(stderr, "stdin: long line\n");
406 	    continue;
407 	}
408 	/* Kill evil characters in MID */
409 	(buf+BUFO)[strcspn(buf+BUFO, " \t\n")]='\0';
410 	/* first syntax check */
411 	if (buf[BUFO]!='<' || (buf+BUFO)[strlen(buf+BUFO)-1]!='>') {
412 #ifdef INN2
413             if (buf[BUFO]!='[' || (buf+BUFO)[strlen(buf+BUFO)-1]!=']') {
414 #endif
415                 fprintf(stderr, "stdin: syntax error\n");
416                 continue;
417 #ifdef INN2
418             }
419 #endif
420         }
421 	do_one(buf+BUFO, 1);
422 	if (cancelid && (strncmp(buf+BUFO, buf, BUFO)!=0)) {
423 	    /* check for <cancel...> ID, if original
424 	       ID is not cancel itself */
425 	    buf[BUFO]='.';
426 	    do_one(buf, 0);
427 	}
428     }
429     if (l) {
430 	openlog("fastcancel", LOG_NDELAY, LOG_NEWS);
431 	syslog(LOGLEVEL, "deleted %d arts %d files, added %d arts",
432 	       sdela, sdelf, sadda);
433     }
434     exit(0);
435 }
436