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