chiark / gitweb /
Add __oop-read-copy.c
[inn-innduct.git] / innd / art.c
1 /*  $Id: art.c 7748 2008-04-06 13:49:56Z iulius $
2 **
3 **  Article-processing.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include <sys/uio.h>
9
10 #include "inn/innconf.h"
11 #include "inn/wire.h"
12 #include "inn/md5.h"
13 #include "innd.h"
14 #include "ov.h"
15 #include "storage.h"
16
17 typedef struct iovec    IOVEC;
18
19 #define ARTIOVCNT       16
20
21 extern bool DoCancels;
22
23 #if     defined(S_IXUSR)
24 #define EXECUTE_BITS    (S_IXUSR | S_IXGRP | S_IXOTH)
25 #else
26 #define EXECUTE_BITS    0111
27 #endif  /* defined(S_IXUSR) */
28
29 /* Characters used in log messages indicating the disposition of messages. */
30 #define ART_ACCEPT              '+'
31 #define ART_CANC                'c'
32 #define ART_STRSTR              '?'
33 #define ART_JUNK                'j'
34 #define ART_REJECT              '-'
35
36 /*
37 **  used to sort Xref, Bytes and Path pointers
38 */
39 typedef struct _HEADERP {
40   int   index;                          
41   char  *p;
42 } HEADERP;
43   
44 #define HPCOUNT         4
45
46 /*
47 **  For speed we build a binary tree of the headers, sorted by their
48 **  name.  We also store the header's Name fields in the tree to avoid
49 **  doing an extra indirection.
50 */
51 typedef struct _TREE {
52   const char    *Name;
53   const ARTHEADER *Header;
54   struct _TREE  *Before;
55   struct _TREE  *After;
56 } TREE;
57
58 static TREE     *ARTheadertree;
59
60 /*
61 **  For doing the overview database, we keep a list of the headers and
62 **  a flag saying if they're written in brief or full format.
63 */
64 typedef struct _ARTOVERFIELD {
65   const ARTHEADER *Header;
66   bool          NeedHeader;
67 } ARTOVERFIELD;
68
69 static ARTOVERFIELD     *ARTfields;
70
71 /*
72 **  General newsgroup we care about, and what we put in the Path line.
73 */
74 static char     ARTctl[] = "control";
75 static char     ARTjnk[] = "junk";
76 static char     *ARTpathme;
77
78 /*
79 **  Different types of rejected articles.
80 */
81 typedef enum {REJECT_DUPLICATE, REJECT_SITE, REJECT_FILTER, REJECT_DISTRIB,
82               REJECT_GROUP, REJECT_UNAPP, REJECT_OTHER} Reject_type;
83
84 /*
85 **  Flag array, indexed by character.  Character classes for Message-ID's.
86 */
87 static char             ARTcclass[256];
88 #define CC_MSGID_ATOM   01
89 #define CC_MSGID_NORM   02
90 #define CC_HOSTNAME     04
91 #define ARTnormchar(c)  ((ARTcclass[(unsigned char)(c)] & CC_MSGID_NORM) != 0)
92 #define ARTatomchar(c)  ((ARTcclass[(unsigned char)(c)] & CC_MSGID_ATOM) != 0)
93 #define ARThostchar(c)  ((ARTcclass[(unsigned char)(c)] & CC_HOSTNAME) != 0)
94
95 #if defined(DO_PERL) || defined(DO_PYTHON)
96 const char      *filterPath;
97 #endif /* DO_PERL || DO_PYTHON */
98
99
100 \f
101 /*
102 **  Trim '\r' from buffer.
103 */
104 static void
105 buffer_trimcr(struct buffer *bp)
106 {
107     char *p, *q;
108     int trimmed = 0;
109
110     for (p = q = bp->data ; p < bp->data + bp->left ; p++) {
111         if (*p == '\r' && p+1 < bp->data + bp->left && p[1] == '\n') {
112             trimmed++;
113             continue;
114         }
115         *q++ = *p;
116     }
117     bp->left -= trimmed;
118 }
119
120 /*
121 **  Mark that the site gets this article.
122 */
123 static void
124 SITEmark(SITE *sp, NEWSGROUP *ngp)
125 {
126   SITE  *funnel;
127
128   sp->Sendit = true;
129   if (sp->ng == NULL)
130     sp->ng = ngp;
131   if (sp->Funnel != NOSITE) {
132     funnel = &Sites[sp->Funnel];
133     if (funnel->ng == NULL)
134       funnel->ng = ngp;
135   }
136 }
137
138 /*
139 **
140 */
141 bool
142 ARTreadschema(void)
143 {
144   static char   *SCHEMA = NULL;
145   FILE          *F;
146   int           i;
147   char          *p;
148   ARTOVERFIELD  *fp;
149   const ARTHEADER *hp;
150   bool          ok;
151   char          buff[SMBUF];
152   bool          foundxref = false;
153   bool          foundxreffull = false;
154
155   if (ARTfields != NULL) {
156     free(ARTfields);
157     ARTfields = NULL;
158   }
159
160   /* Open file, count lines. */
161   if (SCHEMA == NULL)
162     SCHEMA = concatpath(innconf->pathetc, _PATH_SCHEMA);
163   if ((F = Fopen(SCHEMA, "r", TEMPORARYOPEN)) == NULL)
164     return false;
165   for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++)
166     continue;
167   fseeko(F, 0, SEEK_SET);
168   ARTfields = xmalloc((i + 1) * sizeof(ARTOVERFIELD));
169
170   /* Parse each field. */
171   for (ok = true, fp = ARTfields ; fgets(buff, sizeof buff, F) != NULL ;) {
172     /* Ignore blank and comment lines. */
173     if ((p = strchr(buff, '\n')) != NULL)
174       *p = '\0';
175     if ((p = strchr(buff, '#')) != NULL)
176       *p = '\0';
177     if (buff[0] == '\0')
178       continue;
179     if ((p = strchr(buff, ':')) != NULL) {
180       *p++ = '\0';
181       fp->NeedHeader = (strcmp(p, "full") == 0);
182     } else
183       fp->NeedHeader = false;
184     if (strcasecmp(buff, "Xref") == 0) {
185       foundxref = true;
186       foundxreffull = fp->NeedHeader;
187     }
188     for (hp = ARTheaders; hp < ARRAY_END(ARTheaders); hp++) {
189       if (strcasecmp(buff, hp->Name) == 0) {
190         fp->Header = hp;
191         break;
192       }
193     }
194     if (hp == ARRAY_END(ARTheaders)) {
195       syslog(L_ERROR, "%s bad_schema unknown header \"%s\"",
196                 LogName, buff);
197       ok = false;
198       continue;
199     }
200     fp++;
201   }
202   fp->Header = NULL;
203
204   Fclose(F);
205   if (!foundxref || !foundxreffull) {
206     syslog(L_FATAL, "%s 'Xref:full' must be included in %s", LogName, SCHEMA);
207     exit(1);
208   }
209   return ok;
210 }
211
212
213 /*
214 **  Build a balanced tree for the headers in subscript range [lo..hi).
215 **  This only gets called once, and the tree only has about 37 entries,
216 **  so we don't bother to unroll the recursion.
217 */
218 static TREE *
219 ARTbuildtree(const ARTHEADER **Table, int lo, int hi)
220 {
221   int   mid;
222   TREE  *tp;
223
224   mid = lo + (hi - lo) / 2;
225   tp = xmalloc(sizeof(TREE));
226   tp->Header = Table[mid];
227   tp->Name = tp->Header->Name;
228   if (mid == lo)
229     tp->Before = NULL;
230   else
231     tp->Before = ARTbuildtree(Table, lo, mid);
232   if (mid == hi - 1)
233     tp->After = NULL;
234   else
235     tp->After = ARTbuildtree(Table, mid + 1, hi);
236   return tp;
237 }
238
239
240 /*
241 **  Sorting predicate for qsort call in ARTsetup.
242 */
243 static int
244 ARTcompare(const void *p1, const void *p2)
245 {
246   return strcasecmp(((const ARTHEADER **)p1)[0]->Name,
247     ((const ARTHEADER **)p2)[0]->Name);
248 }
249
250
251 /*
252 **  Setup the article processing.
253 */
254 void
255 ARTsetup(void)
256 {
257   const char *  p;
258   const ARTHEADER **    table;
259   unsigned int  i;
260
261   /* Set up the character class tables.  These are written a
262    * little strangely to work around a GCC2.0 bug. */
263   memset(ARTcclass, 0, sizeof ARTcclass);
264   p = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
265   while ((i = *p++) != 0) {
266     ARTcclass[i] = CC_HOSTNAME | CC_MSGID_ATOM | CC_MSGID_NORM;
267   }
268   p = "!#$%&'*+-/=?^_`{|}~";
269   while ((i = *p++) != 0) {
270     ARTcclass[i] = CC_MSGID_ATOM | CC_MSGID_NORM;
271   }
272   p = "\"(),.:;<@[\\]";
273   while ((i = *p++) != 0) {
274     ARTcclass[i] = CC_MSGID_NORM;
275   }
276
277   /* The RFC's don't require it, but we add underscore to the list of valid
278    * hostname characters. */
279   ARTcclass['.'] |= CC_HOSTNAME;
280   ARTcclass['-'] |= CC_HOSTNAME;
281   ARTcclass['_'] |= CC_HOSTNAME;
282
283   /* Build the header tree. */
284   table = xmalloc(ARRAY_SIZE(ARTheaders) * sizeof(ARTHEADER *));
285   for (i = 0; i < ARRAY_SIZE(ARTheaders); i++)
286     table[i] = &ARTheaders[i];
287   qsort(table, ARRAY_SIZE(ARTheaders), sizeof *table, ARTcompare);
288   ARTheadertree = ARTbuildtree(table, 0, ARRAY_SIZE(ARTheaders));
289   free(table);
290
291   /* Get our Path name, kill trailing !. */
292   ARTpathme = xstrdup(Path.data);
293   ARTpathme[Path.used - 1] = '\0';
294
295   /* Set up database; ignore errors. */
296   ARTreadschema();
297 }
298
299
300 static void
301 ARTfreetree(TREE *tp)
302 {
303   TREE  *next;
304
305   for ( ; tp != NULL; tp = next) {
306     if (tp->Before)
307       ARTfreetree(tp->Before);
308     next = tp->After;
309     free(tp);
310   }
311 }
312
313
314 void
315 ARTclose(void)
316 {
317   if (ARTfields != NULL) {
318     free(ARTfields);
319     ARTfields = NULL;
320   }
321   ARTfreetree(ARTheadertree);
322 }
323
324 /*
325 **  Start a log message about an article.
326 */
327 static void
328 ARTlog(const ARTDATA *data, char code, const char *text)
329 {
330   const HDRCONTENT *hc = data->HdrContent;
331   int i;
332   bool Done;
333
334   TMRstart(TMR_ARTLOG);
335   /* We could be a bit faster by not dividing Now.usec by 1000,
336    * but who really wants to log at the Microsec level? */
337   Done = code == ART_ACCEPT || code == ART_JUNK;
338   if (text)
339     i = fprintf(Log, "%.15s.%03d %c %s %s %s%s",
340       ctime(&Now.time) + 4, (int)(Now.usec / 1000), code, data->Feedsite,
341       HDR_FOUND(HDR__MESSAGE_ID) ? HDR(HDR__MESSAGE_ID) : "(null)",
342       text, Done ? "" : "\n");
343   else
344     i = fprintf(Log, "%.15s.%03d %c %s %s%s",
345       ctime(&Now.time) + 4, (int)(Now.usec / 1000), code, data->Feedsite,
346       HDR_FOUND(HDR__MESSAGE_ID) ? HDR(HDR__MESSAGE_ID) : "(null)",
347       Done ? "" : "\n");
348   if (i == EOF || (Done && !BufferedLogs && fflush(Log)) || ferror(Log)) {
349     i = errno;
350     syslog(L_ERROR, "%s cant write log_start %m", LogName);
351     IOError("logging article", i);
352     clearerr(Log);
353   }
354   TMRstop(TMR_ARTLOG);
355 }
356
357 /*
358 **  Parse a Path line, splitting it up into NULL-terminated array of strings.
359 */
360 static int
361 ARTparsepath(const char *p, int size, LISTBUFFER *list)
362 {
363   int   i;
364   char  *q, **hp;
365
366   /* setup buffer */ 
367   SetupListBuffer(size, list);
368
369   /* loop over text and copy */
370   for (i = 0, q = list->Data, hp = list->List ; *p ; p++, *q++ = '\0') { 
371     /* skip leading separators. */
372     for (; *p && !ARThostchar(*p) && ISWHITE(*p) ; p++)
373       continue;
374     if (*p == '\0')
375       break;
376
377     if (list->ListLength <= i) {
378       list->ListLength += DEFAULTNGBOXSIZE;
379       list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
380       hp = &list->List[i];
381     }
382     /* mark the start of the host, move to the end of it while copying */  
383     for (*hp++ = q, i++ ; *p && ARThostchar(*p) && !ISWHITE(*p) ;)
384       *q++ = *p++;
385     if (*p == '\0')
386       break;
387   }
388   *q = '\0';
389   if (i == list->ListLength) {
390     list->ListLength += DEFAULTNGBOXSIZE;
391     list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
392     hp = &list->List[i];
393   }
394   *hp = NULL;
395   return i;
396 }
397
398 /*
399 **  Sorting pointer where header starts
400 */
401 static int
402 ARTheaderpcmp(const void *p1, const void *p2)
403 {
404   return (((const HEADERP *)p1)->p - ((const HEADERP *)p2)->p);
405 }
406
407 /* Write an article using the storage api.  Put it together in memory and
408    call out to the api. */
409 static TOKEN
410 ARTstore(CHANNEL *cp)
411 {
412   struct buffer *Article = &cp->In;
413   ARTDATA       *data = &cp->Data;
414   HDRCONTENT    *hc = data->HdrContent;
415   const char    *p;
416   ARTHANDLE     arth;
417   int           i, j, iovcnt = 0;
418   long          headersize = 0;
419   TOKEN         result;
420   struct buffer *headers = &data->Headers;
421   struct iovec  iov[ARTIOVCNT];
422   HEADERP       hp[HPCOUNT];
423
424   /* find Path, Bytes and Xref to be prepended/dropped/replaced */
425   arth.len = i = 0;
426   /* assumes Path header is required header */
427   hp[i].p = HDR(HDR__PATH);
428   hp[i++].index = HDR__PATH;
429   if (HDR_FOUND(HDR__XREF)) {
430     hp[i].p = HDR(HDR__XREF);
431     hp[i++].index = HDR__XREF;
432   }
433   if (HDR_FOUND(HDR__BYTES)) {
434     hp[i].p = HDR(HDR__BYTES);
435     hp[i++].index = HDR__BYTES;
436   }
437   /* get the order of header appearance */
438   qsort(hp, i, sizeof(HEADERP), ARTheaderpcmp);
439   /* p always points where the next data should be written from */
440   for (p = Article->data + cp->Start, j = 0 ; j < i ; j++) {
441     switch (hp[j].index) {
442       case HDR__PATH:
443         if (!data->Hassamepath || data->AddAlias || Pathcluster.used) {
444           /* write heading data */
445           iov[iovcnt].iov_base = (char *) p;
446           iov[iovcnt++].iov_len = HDR(HDR__PATH) - p;
447           arth.len += HDR(HDR__PATH) - p;
448           /* append clusterpath */
449           if (Pathcluster.used) {
450             iov[iovcnt].iov_base = Pathcluster.data;
451             iov[iovcnt++].iov_len = Pathcluster.used;
452             arth.len += Pathcluster.used;
453           }
454           /* now append new one */
455           iov[iovcnt].iov_base = Path.data;
456           iov[iovcnt++].iov_len = Path.used;
457           arth.len += Path.used;
458           if (data->AddAlias) {
459             iov[iovcnt].iov_base = Pathalias.data;
460             iov[iovcnt++].iov_len = Pathalias.used;
461             arth.len += Pathalias.used;
462           }
463           /* next to write */
464           p = HDR(HDR__PATH);
465           if (data->Hassamecluster)
466             p += Pathcluster.used;
467         }
468         break;
469       case HDR__XREF:
470         if (!innconf->xrefslave) {
471           /* write heading data */
472           iov[iovcnt].iov_base = (char *) p;
473           iov[iovcnt++].iov_len = HDR(HDR__XREF) - p;
474           arth.len += HDR(HDR__XREF) - p;
475           /* replace with new one */
476           iov[iovcnt].iov_base = data->Xref;
477           iov[iovcnt++].iov_len = data->XrefLength - 2;
478           arth.len += data->XrefLength - 2;
479           /* next to write */
480           /* this points where trailing "\r\n" of orginal Xref header exists */
481           p = HDR(HDR__XREF) + HDR_LEN(HDR__XREF);
482         }
483         break;
484       case HDR__BYTES:
485         /* ditch whole Byte header */
486         /* write heading data */
487         iov[iovcnt].iov_base = (char *) p;
488         iov[iovcnt++].iov_len = data->BytesHeader - p;
489         arth.len += data->BytesHeader - p;
490         /* next to write */
491         /* need to skip trailing "\r\n" of Bytes header */
492         p = HDR(HDR__BYTES) + HDR_LEN(HDR__BYTES) + 2;
493         break;
494       default:
495         result.type = TOKEN_EMPTY;
496         return result;
497     }
498   }
499   /* in case Xref is not included in orignal article */
500   if (!HDR_FOUND(HDR__XREF)) {
501     /* write heading data */
502     iov[iovcnt].iov_base = (char *) p;
503     iov[iovcnt++].iov_len = Article->data + (data->Body - 2) - p;
504     arth.len += Article->data + (data->Body - 2) - p;
505     /* Xref needs to be inserted */
506     iov[iovcnt].iov_base = (char *) "Xref: ";
507     iov[iovcnt++].iov_len = sizeof("Xref: ") - 1;
508     arth.len += sizeof("Xref: ") - 1;
509     iov[iovcnt].iov_base = data->Xref;
510     iov[iovcnt++].iov_len = data->XrefLength;
511     arth.len += data->XrefLength;
512     p = Article->data + (data->Body - 2);
513   }
514   /* write rest of data */
515   iov[iovcnt].iov_base = (char *) p;
516   iov[iovcnt++].iov_len = Article->data + cp->Next - p;
517   arth.len += Article->data + cp->Next - p;
518
519   /* revert trailing '\0\n' to '\r\n' of all system header */
520   for (i = 0 ; i < MAX_ARTHEADER ; i++) {
521     if (HDR_FOUND(i))
522       HDR_PARSE_END(i);
523   }
524
525   arth.iov = iov;
526   arth.iovcnt = iovcnt;
527   arth.arrived = (time_t)0;
528   arth.token = (TOKEN *)NULL;
529   arth.expires = data->Expires;
530   if (innconf->storeonxref) {
531     arth.groups = data->Replic;
532     arth.groupslen = data->ReplicLength;
533   } else {
534     arth.groups = HDR(HDR__NEWSGROUPS);
535     arth.groupslen = HDR_LEN(HDR__NEWSGROUPS);
536   }
537
538   SMerrno = SMERR_NOERROR;
539   result = SMstore(arth);
540   if (result.type == TOKEN_EMPTY) {
541     if (SMerrno == SMERR_NOMATCH)
542       ThrottleNoMatchError();
543     else if (SMerrno != SMERR_NOERROR)
544       IOError("SMstore", SMerrno);
545     return result;
546   }
547
548   /* calculate stored size */
549   for (data->BytesValue = i = 0 ; i < iovcnt ; i++) {
550     if (NeedHeaders && (i + 1 == iovcnt)) {
551       /* body begins at last iov */
552       headersize = data->BytesValue +
553         Article->data + data->Body - (char *) iov[i].iov_base;
554     }
555     data->BytesValue += iov[i].iov_len;
556   }
557   /* "\r\n" is counted as 1 byte.  trailing ".\r\n" and body delimitor are also
558      substituted */
559   data->BytesValue -= (data->HeaderLines + data->Lines + 4);
560   /* Figure out how much space we'll need and get it. */
561   snprintf(data->Bytes, sizeof(data->Bytes), "Bytes: %ld\r\n",
562            data->BytesValue);
563   /* does not include strlen("Bytes: \r\n") */
564   data->BytesLength = strlen(data->Bytes) - 9;
565
566   if (!NeedHeaders)
567     return result;
568
569   /* Add the data. */
570   buffer_resize(headers, headersize);
571   buffer_set(headers, data->Bytes, strlen(data->Bytes));
572   for (i = 0 ; i < iovcnt ; i++) {
573     if (i + 1 == iovcnt)
574       buffer_append(headers, iov[i].iov_base,
575         Article->data + data->Body - (char *) iov[i].iov_base);
576     else
577       buffer_append(headers, iov[i].iov_base, iov[i].iov_len);
578   }
579   buffer_trimcr(headers);
580
581   return result;
582 }
583
584 /*
585 **  Parse a header that starts at header.  size includes trailing "\r\n"
586 */
587 static void
588 ARTparseheader(CHANNEL *cp, int size)
589 {
590   ARTDATA       *data = &cp->Data;
591   char          *header = cp->In.data + data->CurHeader;
592   HDRCONTENT    *hc = cp->Data.HdrContent;
593   TREE          *tp;
594   const ARTHEADER *hp;
595   char          c, *p, *colon;
596   int           i;
597
598   /* Find first colon */
599   if ((colon = memchr(header, ':', size)) == NULL || !ISWHITE(colon[1])) {
600     if ((p = memchr(header, '\r', size)) != NULL)
601       *p = '\0';
602     snprintf(cp->Error, sizeof(cp->Error),
603              "%d No colon-space in \"%s\" header",
604              NNTP_REJECTIT_VAL, MaxLength(header, header));
605     if (p != NULL)
606       *p = '\r';
607     return;
608   }
609
610   /* See if this is a system header.  A fairly tightly-coded binary search. */
611   c = CTYPE(islower, *header) ? toupper(*header) : *header;
612   for (*colon = '\0', tp = ARTheadertree; tp; ) {
613     if ((i = c - tp->Name[0]) == 0 && (i = strcasecmp(header, tp->Name)) == 0)
614       break;
615     if (i < 0)
616       tp = tp->Before;
617     else
618       tp = tp->After;
619   }
620   *colon = ':';
621
622   if (tp == NULL) {
623     /* Not a system header, make sure we have <word><colon><space>. */
624     for (p = colon; --p > header; ) {
625       if (ISWHITE(*p)) {
626         c = *p;
627         *p = '\0';
628         snprintf(cp->Error, sizeof(cp->Error),
629                  "%d Space before colon in \"%s\" header",
630                  NNTP_REJECTIT_VAL, MaxLength(header, header));
631         *p = c;
632         return;
633       }
634     }
635     return;
636   }
637   hp = tp->Header;
638   i = hp - ARTheaders;
639   /* remember to ditch if it's Bytes: */
640   if (i == HDR__BYTES)
641     cp->Data.BytesHeader = header;
642   hc = &hc[i];
643   if (hc->Length != 0) {
644     /* duplicated */
645     hc->Length = -1;
646   } else {
647     for (p = colon + 1 ; (p < header + size - 2) &&
648       (ISWHITE(*p) || *p == '\r' || *p == '\n'); p++);
649     if (p < header + size - 2) {
650       hc->Value = p;
651       /* HDR_LEN() does not include trailing "\r\n" */
652       hc->Length = header + size - 2 - p;
653     } else {
654       snprintf(cp->Error, sizeof(cp->Error),
655                "%d Body of header is all blanks in \"%s\" header",
656                NNTP_REJECTIT_VAL, MaxLength(hp->Name, hp->Name));
657     }
658   }
659   return;
660 }
661
662 /*
663 **  Check Message-ID format based on RFC 822 grammar, except that (as per
664 **  RFC 1036) whitespace, non-printing, and '>' characters are excluded.
665 **  Based on code by Paul Eggert posted to news.software.b on 22-Nov-90
666 **  in <#*tyo2'~n@twinsun.com>, with additional email discussion.
667 **  Thanks, Paul.
668 */
669 bool
670 ARTidok(const char *MessageID)
671 {
672   int           c;
673   const char    *p;
674
675   /* Check the length of the message ID. */
676   if (MessageID == NULL || strlen(MessageID) > NNTP_MSGID_MAXLEN)
677     return false;
678
679   /* Scan local-part:  "< atom|quoted [ . atom|quoted]" */
680   p = MessageID;
681   if (*p++ != '<')
682     return false;
683   for (; ; p++) {
684     if (ARTatomchar(*p))
685       while (ARTatomchar(*++p))
686         continue;
687     else {
688       if (*p++ != '"')
689         return false;
690       for ( ; ; ) {
691         switch (c = *p++) {
692         case '\\':
693           c = *p++;
694           /* FALLTHROUGH */
695         default:
696           if (ARTnormchar(c))
697             continue;
698           return false;
699         case '"':
700           break;
701         }
702         break;
703       }
704     }
705     if (*p != '.')
706       break;
707   }
708
709   /* Scan domain part:  "@ atom|domain [ . atom|domain] > \0" */
710   if (*p++ != '@')
711     return false;
712   for ( ; ; p++) {
713     if (ARTatomchar(*p))
714       while (ARTatomchar(*++p))
715         continue;
716     else {
717       if (*p++ != '[')
718         return false;
719       for ( ; ; ) {
720         switch (c = *p++) {
721         case '\\':
722           c = *p++;
723           /* FALLTHROUGH */
724         default:
725           if (ARTnormchar(c))
726             continue;
727           /* FALLTHROUGH */
728         case '[':
729           return false;
730         case ']':
731           break;
732         }
733         break;
734       }
735     }
736     if (*p != '.')
737       break;
738   }
739
740   return *p == '>' && *++p == '\0';
741 }
742
743 /*
744 **  Clean up data field where article informations are stored.
745 **  This must be called before article processing.
746 */
747 void
748 ARTprepare(CHANNEL *cp)
749 {
750   ARTDATA       *data = &cp->Data;
751   HDRCONTENT    *hc = data->HdrContent;
752   int           i;
753
754   for (i = 0 ; i < MAX_ARTHEADER ; i++, hc++) {
755     hc->Value = NULL;
756     hc->Length = 0;
757   }
758   data->Lines = data->HeaderLines = data->CRwithoutLF = data->LFwithoutCR = 0;
759   data->CurHeader = data->LastTerminator = data->LastCR = cp->Start - 1;
760   data->LastCRLF = data->Body = cp->Start - 1;
761   data->BytesHeader = NULL;
762   data->Feedsite = "?";
763   *cp->Error = '\0';
764 }
765
766 /*
767 **  Clean up an article.  This is mainly copying in-place, stripping bad
768 **  headers.  Also fill in the article data block with what we can find.
769 **  Return NULL if the article is okay, or a string describing the error.
770 **  Parse headers and end of article
771 **  This is called by NCproc().
772 */
773 void
774 ARTparse(CHANNEL *cp)
775 {
776   struct buffer *bp = &cp->In;
777   ARTDATA       *data = &cp->Data;
778   long          i, limit, fudge, size;
779   int           hopcount;
780   char          **hops;
781   HDRCONTENT    *hc = data->HdrContent;
782
783   /* Read through the buffer to find header, body and end of article */
784   /* this routine is designed not to refer data so long as possible for
785      performance reason, so the code may look redundant at a glance */
786   limit = bp->used;
787   i = cp->Next;
788   if (cp->State == CSgetheader) {
789     /* header processing */
790     for (; i < limit ;) {
791       if (data->LastCRLF + 1 == i) {
792         /* begining of the line */
793         switch (bp->data[i]) {
794           case '.':
795             data->LastTerminator = i;
796             data->NullHeader = false;
797             break;
798           case '\r':
799             data->LastCR = i;
800             data->NullHeader = false;
801             break;
802           case '\n':
803             data->LFwithoutCR++;
804             data->NullHeader = false;
805             break;
806           case '\t':
807           case ' ':
808             /* header is folded.  NullHeader is untouched */
809             break;
810           case '\0':
811             snprintf(cp->Error, sizeof(cp->Error), "%d Null Header",
812                      NNTP_REJECTIT_VAL);
813             data->NullHeader = true;
814             break;
815           default:
816             if (data->CurHeader >= cp->Start) {
817               /* parse previous header */
818               if (!data->NullHeader && (*cp->Error == '\0'))
819                 /* skip if already got an error */
820                 ARTparseheader(cp, i - data->CurHeader);
821             }
822             data->CurHeader = i;
823             data->NullHeader = false;
824             break;
825         }
826         i++;
827       }
828       for (; i < limit ;) {
829         /* rest of the line */
830         switch (bp->data[i]) {
831           case '\0':
832             snprintf(cp->Error, sizeof(cp->Error), "%d Null Header",
833                      NNTP_REJECTIT_VAL);
834             data->NullHeader = true;
835             break;
836           case '\r':
837             if (data->LastCR >= cp->Start)
838               data->CRwithoutLF++;
839             data->LastCR = i;
840             break;
841           case '\n':
842             if (data->LastCR + 1 == i) {
843               /* found CRLF */
844               data->LastCR = cp->Start - 1;
845               if (data->LastTerminator + 2 == i) {
846                 /* terminated still in header */
847                 if (cp->Start + 3 == i) {
848                   snprintf(cp->Error, sizeof(cp->Error), "%d Empty article",
849                            NNTP_REJECTIT_VAL);
850                   cp->State = CSnoarticle;
851                 } else {
852                   snprintf(cp->Error, sizeof(cp->Error), "%d No body",
853                            NNTP_REJECTIT_VAL);
854                   cp->State = CSgotarticle;
855                 }
856                 cp->Next = ++i;
857                 goto sizecheck;
858               }
859               if (data->LastCRLF + MAXHEADERSIZE < i)
860                 snprintf(cp->Error, sizeof(cp->Error),
861                          "%d Too long line in header %ld bytes",
862                          NNTP_REJECTIT_VAL, i - data->LastCRLF);
863               else if (data->LastCRLF + 2 == i) {
864                 /* header ends */
865                 /* parse previous header */
866                 if (data->CurHeader >= cp->Start) {
867                   if (!data->NullHeader && (*cp->Error == '\0'))
868                     /* skip if already got an error */
869                     ARTparseheader(cp, i - 1 - data->CurHeader);
870                 } else {
871                   snprintf(cp->Error, sizeof(cp->Error), "%d No header",
872                            NNTP_REJECTIT_VAL);
873                 }
874                 data->LastCRLF = i++;
875                 data->Body = i;
876                 cp->State = CSgetbody;
877                 goto bodyprocessing;
878               }
879               data->HeaderLines++;
880               data->LastCRLF = i++;
881               goto endofheaderline;
882             } else {
883               data->LFwithoutCR++;
884             }
885             break;
886           default:
887             break;
888         }
889         i++;
890       }
891 endofheaderline:
892       ;
893     }
894   } else {
895 bodyprocessing:
896     /* body processing, or eating huge article */
897     for (; i < limit ;) {
898       if (data->LastCRLF + 1 == i) {
899         /* begining of the line */
900         switch (bp->data[i]) {
901           case '.':
902             data->LastTerminator = i;
903             break;
904           case '\r':
905             data->LastCR = i;
906             break;
907           case '\n':
908             data->LFwithoutCR++;
909             break;
910           default:
911             break;
912         }
913         i++;
914       }
915       for (; i < limit ;) {
916         /* rest of the line */
917         switch (bp->data[i]) {
918           case '\r':
919             if (data->LastCR >= cp->Start)
920               data->CRwithoutLF++;
921             data->LastCR = i;
922             break;
923           case '\n':
924             if (data->LastCR + 1 == i) {
925               /* found CRLF */
926               data->LastCR = cp->Start - 1;
927               if (data->LastTerminator + 2 == i) {
928                 /* found end of article */
929                 if (cp->State == CSeatarticle) {
930                   cp->State = CSgotlargearticle;
931                   cp->Next = ++i;
932                   snprintf(cp->Error, sizeof(cp->Error),
933                     "%d Article of %ld bytes exceeds local limit of %ld bytes",
934                     NNTP_REJECTIT_VAL, (unsigned long) i - cp->Start,
935                     innconf->maxartsize);
936                 } else {
937                   cp->State = CSgotarticle;
938                   i++;
939                 }
940                 if (*cp->Error != '\0' && HDR_FOUND(HDR__MESSAGE_ID)) {
941                   HDR_PARSE_START(HDR__MESSAGE_ID);
942                   if (HDR_FOUND(HDR__PATH)) {
943                     /* to record path into news log */
944                     HDR_PARSE_START(HDR__PATH);
945                     hopcount = ARTparsepath(HDR(HDR__PATH), HDR_LEN(HDR__PATH),
946                       &data->Path);
947                     HDR_PARSE_END(HDR__PATH);
948                     if (hopcount > 0) {
949                       hops = data->Path.List;
950                       if (innconf->logipaddr) {
951                         data->Feedsite = RChostname(cp);
952                         if (data->Feedsite == NULL)
953                           data->Feedsite = CHANname(cp);
954                         if (strcmp("0.0.0.0", data->Feedsite) == 0 ||
955                           data->Feedsite[0] == '\0')
956                           data->Feedsite =
957                             hops && hops[0] ? hops[0] : CHANname(cp);
958                       } else {
959                         data->Feedsite =
960                           hops && hops[0] ? hops[0] : CHANname(cp);
961                       }
962                     }
963                   }
964                   ARTlog(data, ART_REJECT, cp->Error);
965                   HDR_PARSE_END(HDR__MESSAGE_ID);
966                 }
967                 if (cp->State == CSgotlargearticle)
968                   return;
969                 goto sizecheck;
970               }
971 #if 0 /* this may be examined in the future */
972               if (data->LastCRLF + MAXHEADERSIZE < i)
973                 snprintf(cp->Error, sizeof(cp->Error),
974                          "%d Too long line in body %d bytes",
975                          NNTP_REJECTIT_VAL, i);
976 #endif
977               data->Lines++;
978               data->LastCRLF = i++;
979               goto endofline;
980             } else {
981               data->LFwithoutCR++;
982             }
983             break;
984           default:
985             break;
986         }
987         i++;
988       }
989 endofline:
990       ;
991     }
992   }
993 sizecheck:
994   size = i - cp->Start;
995   fudge = data->HeaderLines + data->Lines + 4;
996   if (innconf->maxartsize > 0)
997     if (size > fudge && size - fudge > innconf->maxartsize)
998         cp->State = CSeatarticle;
999   cp->Next = i;
1000   return;
1001 }
1002
1003 /*
1004 **  Clean up an article.  This is mainly copying in-place, stripping bad
1005 **  headers.  Also fill in the article data block with what we can find.
1006 **  Return true if the article has no error, or false which means the error.
1007 */
1008 static bool
1009 ARTclean(ARTDATA *data, char *buff)
1010 {
1011   HDRCONTENT    *hc = data->HdrContent;
1012   const ARTHEADER *hp = ARTheaders;
1013   int           i;
1014   char          *p;
1015   int           delta;
1016
1017   TMRstart(TMR_ARTCLEAN);
1018   data->Arrived = Now.time;
1019   data->Expires = 0;
1020
1021   /* replace trailing '\r\n' with '\0\n' of all system header to be handled
1022      easily by str*() functions */
1023   for (i = 0 ; i < MAX_ARTHEADER ; i++) {
1024     if (HDR_FOUND(i))
1025       HDR_PARSE_START(i);
1026   }
1027
1028   /* Make sure all the headers we need are there */
1029   for (i = 0; i < MAX_ARTHEADER ; i++) {
1030     if (hp[i].Type == HTreq) {
1031       if (HDR_FOUND(i))
1032         continue;
1033       if (hc[i].Length < 0) {
1034         sprintf(buff, "%d Duplicate \"%s\" header", NNTP_REJECTIT_VAL,
1035                 hp[1].Name);
1036       } else {
1037         sprintf(buff, "%d Missing \"%s\" header", NNTP_REJECTIT_VAL,
1038                 hp[i].Name);
1039       }
1040       TMRstop(TMR_ARTCLEAN);
1041       return false;
1042     }
1043   }
1044
1045   /* assumes Message-ID header is required header */
1046   if (!ARTidok(HDR(HDR__MESSAGE_ID))) {
1047     HDR_LEN(HDR__MESSAGE_ID) = 0;
1048     sprintf(buff, "%d Bad \"Message-ID\" header", NNTP_REJECTIT_VAL);
1049     TMRstop(TMR_ARTCLEAN);
1050     return false;
1051   }
1052
1053   if (innconf->linecountfuzz && HDR_FOUND(HDR__LINES)) {
1054     p = HDR(HDR__LINES);
1055     i = data->Lines;
1056     if ((delta = i - atoi(p)) != 0 && abs(delta) > innconf->linecountfuzz) {
1057       sprintf(buff, "%d Linecount %s != %d +- %ld", NNTP_REJECTIT_VAL,
1058         MaxLength(p, p), i, innconf->linecountfuzz);
1059       TMRstop(TMR_ARTCLEAN);
1060       return false;
1061     }
1062   }
1063
1064   /* Is article too old? */
1065   /* assumes Date header is required header */
1066   p = HDR(HDR__DATE);
1067   if ((data->Posted = parsedate(p, &Now)) == -1) {
1068     sprintf(buff, "%d Bad \"Date\" header -- \"%s\"", NNTP_REJECTIT_VAL,
1069       MaxLength(p, p));
1070     TMRstop(TMR_ARTCLEAN);
1071     return false;
1072   }
1073   if (innconf->artcutoff) {
1074       long cutoff = innconf->artcutoff * 24 * 60 * 60;
1075
1076       if (data->Posted < Now.time - cutoff) {
1077           sprintf(buff, "%d Too old -- \"%s\"", NNTP_REJECTIT_VAL,
1078                   MaxLength(p, p));
1079           TMRstop(TMR_ARTCLEAN);
1080           return false;
1081       }
1082   }
1083   if (data->Posted > Now.time + DATE_FUZZ) {
1084     sprintf(buff, "%d Article posted in the future -- \"%s\"",
1085       NNTP_REJECTIT_VAL, MaxLength(p, p));
1086     TMRstop(TMR_ARTCLEAN);
1087     return false;
1088   }
1089   if (HDR_FOUND(HDR__EXPIRES)) {
1090     p = HDR(HDR__EXPIRES);
1091     data->Expires = parsedate(p, &Now);
1092   }
1093
1094   /* Colon or whitespace in the Newsgroups header? */
1095   /* assumes Newsgroups header is required header */
1096   if ((data->Groupcount =
1097     NGsplit(HDR(HDR__NEWSGROUPS), HDR_LEN(HDR__NEWSGROUPS),
1098     &data->Newsgroups)) == 0) {
1099     TMRstop(TMR_ARTCLEAN);
1100     sprintf(buff, "%d Unwanted character in \"Newsgroups\" header",
1101       NNTP_REJECTIT_VAL);
1102     return false;
1103   }
1104
1105   /* Fill in other Data fields. */
1106   if (HDR_FOUND(HDR__SENDER))
1107     data->Poster = HDR(HDR__SENDER);
1108   else
1109     data->Poster = HDR(HDR__FROM);
1110   if (HDR_FOUND(HDR__REPLY_TO))
1111     data->Replyto = HDR(HDR__REPLY_TO);
1112   else
1113     data->Replyto = HDR(HDR__FROM);
1114
1115   TMRstop(TMR_ARTCLEAN);
1116   return true;
1117 }
1118
1119 /*
1120 **  We are going to reject an article, record the reason and
1121 **  and the article.
1122 */
1123 static void
1124 ARTreject(Reject_type code, CHANNEL *cp, struct buffer *article UNUSED)
1125 {
1126   /* Remember why the article was rejected (for the status file) */
1127
1128   switch (code) {
1129     case REJECT_DUPLICATE:
1130       cp->Duplicate++;
1131       cp->DuplicateSize += cp->Next - cp->Start;
1132       break;
1133     case REJECT_SITE:
1134       cp->Unwanted_s++;
1135       break;
1136     case REJECT_FILTER:
1137       cp->Unwanted_f++;
1138       break;
1139     case REJECT_DISTRIB:
1140       cp->Unwanted_d++;
1141       break;
1142     case REJECT_GROUP:
1143       cp->Unwanted_g++;
1144       break;
1145     case REJECT_UNAPP:
1146       cp->Unwanted_u++;
1147       break;
1148     case REJECT_OTHER:
1149       cp->Unwanted_o++;
1150       break;
1151     default:
1152       /* should never be here */
1153       syslog(L_NOTICE, "%s unknown reject type received by ARTreject()",
1154              LogName);
1155       break;
1156   }
1157       /* error */
1158 }
1159
1160 /*
1161 **  Verify if a cancel message is valid.  If the user posting the cancel
1162 **  matches the user who posted the article, return the list of filenames
1163 **  otherwise return NULL.
1164 */
1165 static bool
1166 ARTcancelverify(const ARTDATA *data, const char *MessageID, TOKEN *token)
1167 {
1168   const char    *p;
1169   char          *q, *q1;
1170   const char    *local;
1171   char          buff[SMBUF];
1172   ARTHANDLE     *art;
1173   bool          r;
1174
1175   if (!HISlookup(History, MessageID, NULL, NULL, NULL, token))
1176     return false;
1177   if ((art = SMretrieve(*token, RETR_HEAD)) == NULL)
1178     return false;
1179   local = wire_findheader(art->data, art->len, "Sender");
1180   if (local == NULL) {
1181     local = wire_findheader(art->data, art->len, "From");
1182     if (local == NULL) {
1183       SMfreearticle(art);
1184       return false;
1185     }
1186   }
1187   for (p = local; p < art->data + art->len; p++) {
1188     if (*p == '\r' || *p == '\n')
1189       break;
1190   }
1191   if (p == art->data + art->len) {
1192     SMfreearticle(art);
1193     return false;
1194   }
1195   q = xmalloc(p - local + 1);
1196   memcpy(q, local, p - local);
1197   SMfreearticle(art);
1198   q[p - local] = '\0';
1199   HeaderCleanFrom(q);
1200
1201   /* Compare canonical forms. */
1202   q1 = xstrdup(data->Poster);
1203   HeaderCleanFrom(q1);
1204   if (strcmp(q, q1) != 0) {
1205     r = false;
1206     sprintf(buff, "\"%.50s\" wants to cancel %s by \"%.50s\"",
1207       q1, MaxLength(MessageID, MessageID), q);
1208     ARTlog(data, ART_REJECT, buff);
1209   }
1210   else {
1211     r = true;
1212   }
1213   free(q1);
1214   free(q);
1215   return r;
1216 }
1217
1218 /*
1219 **  Process a cancel message.
1220 */
1221 void
1222 ARTcancel(const ARTDATA *data, const char *MessageID, const bool Trusted)
1223 {
1224   char  buff[SMBUF+16];
1225   TOKEN token;
1226   bool  r;
1227
1228   TMRstart(TMR_ARTCNCL);
1229   if (!DoCancels && !Trusted) {
1230     TMRstop(TMR_ARTCNCL);
1231     return;
1232   }
1233
1234   if (!ARTidok(MessageID)) {
1235     syslog(L_NOTICE, "%s bad cancel Message-ID %s", data->Feedsite,
1236       MaxLength(MessageID, MessageID));
1237     TMRstop(TMR_ARTCNCL);
1238     return;
1239   }
1240
1241   if (!HIScheck(History, MessageID)) {
1242     /* Article hasn't arrived here, so write a fake entry using
1243      * most of the information from the cancel message. */
1244     if (innconf->verifycancels && !Trusted) {
1245       TMRstop(TMR_ARTCNCL);
1246       return;
1247     }
1248     InndHisRemember(MessageID);
1249     snprintf(buff, sizeof(buff), "Cancelling %s",
1250              MaxLength(MessageID, MessageID));
1251     ARTlog(data, ART_CANC, buff);
1252     TMRstop(TMR_ARTCNCL);
1253     return;
1254   }
1255   if (Trusted || !innconf->verifycancels)
1256       r = HISlookup(History, MessageID, NULL, NULL, NULL, &token);
1257   else
1258       r = ARTcancelverify(data, MessageID, &token);
1259   if (r == false) {
1260     TMRstop(TMR_ARTCNCL);
1261     return;
1262   }
1263
1264   /* Get stored message and zap them. */
1265   if (!SMcancel(token) && SMerrno != SMERR_NOENT && SMerrno != SMERR_UNINIT)
1266     syslog(L_ERROR, "%s cant cancel %s (SMerrno %d)", LogName,
1267         TokenToText(token), SMerrno);
1268   if (innconf->immediatecancel && !SMflushcacheddata(SM_CANCELEDART))
1269     syslog(L_ERROR, "%s cant cancel cached %s", LogName, TokenToText(token));
1270   snprintf(buff, sizeof(buff), "Cancelling %s",
1271            MaxLength(MessageID, MessageID));
1272   ARTlog(data, ART_CANC, buff);
1273   TMRstop(TMR_ARTCNCL);
1274 }
1275
1276 /*
1277 **  Process a control message.  Cancels are handled here, but any others
1278 **  are passed out to an external program in a specific directory that
1279 **  has the same name as the first word of the control message.
1280 */
1281 static void
1282 ARTcontrol(ARTDATA *data, char *Control, CHANNEL *cp UNUSED)
1283 {
1284   char *p, c;
1285
1286   /* See if it's a cancel message. */
1287   c = *Control;
1288   if (c == 'c' && strncmp(Control, "cancel", 6) == 0) {
1289     for (p = &Control[6]; ISWHITE(*p); p++)
1290       continue;
1291     if (*p && ARTidok(p))
1292       ARTcancel(data, p, false);
1293     return;
1294   }
1295 }
1296
1297 /*
1298 **  Parse a Distribution line, splitting it up into NULL-terminated array of
1299 **  strings.
1300 */
1301 static void
1302 ARTparsedist(const char *p, int size, LISTBUFFER *list)
1303 {
1304   int   i;
1305   char  *q, **dp;
1306
1307   /* setup buffer */ 
1308   SetupListBuffer(size, list);
1309
1310   /* loop over text and copy */
1311   for (i = 0, q = list->Data, dp = list->List ; *p ; p++, *q++ = '\0') { 
1312     /* skip leading separators. */
1313     for (; *p && ((*p == ',') || ISWHITE(*p)) ; p++)
1314       continue;
1315     if (*p == '\0')
1316       break;
1317
1318     if (list->ListLength <= i) {
1319       list->ListLength += DEFAULTNGBOXSIZE;
1320       list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
1321       dp = &list->List[i];
1322     }
1323     /* mark the start of the host, move to the end of it while copying */  
1324     for (*dp++ = q, i++ ; *p && (*p != ',') && !ISWHITE(*p) ;)
1325       *q++ = *p++;
1326     if (*p == '\0')
1327       break;
1328   }
1329   *q = '\0';
1330   if (i == list->ListLength) {
1331     list->ListLength += DEFAULTNGBOXSIZE;
1332     list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
1333     dp = &list->List[i];
1334   }
1335   *dp = NULL;
1336   return;
1337 }
1338
1339 /*
1340 **  A somewhat similar routine, except that this handles negated entries
1341 **  in the list and is used to check the distribution sub-field.
1342 */
1343 static bool
1344 DISTwanted(char **list, char *p)
1345 {
1346   char  *q;
1347   char  c;
1348   bool  sawbang;
1349
1350   for (sawbang = false, c = *p; (q = *list) != NULL; list++) {
1351     if (*q == '!') {
1352       sawbang = true;
1353       if (c == *++q && strcmp(p, q) == 0)
1354         return false;
1355     } else if (c == *q && strcmp(p, q) == 0)
1356       return true;
1357   }
1358
1359   /* If we saw any !foo's and didn't match, then assume they are all negated
1360      distributions and return true, else return false. */
1361   return sawbang;
1362 }
1363
1364 /*
1365 **  See if any of the distributions in the article are wanted by the site.
1366 */
1367 static bool
1368 DISTwantany(char **site, char **article)
1369 {
1370   for ( ; *article; article++)
1371     if (DISTwanted(site, *article))
1372       return true;
1373   return false;
1374 }
1375
1376 /*
1377 **  Send the current article to all sites that would get it if the
1378 **  group were created.
1379 */
1380 static void
1381 ARTsendthegroup(char *name)
1382 {
1383   SITE          *sp;
1384   int           i;
1385   NEWSGROUP     *ngp;
1386
1387   for (ngp = NGfind(ARTctl), sp = Sites, i = nSites; --i >= 0; sp++) {
1388     if (sp->Name != NULL && SITEwantsgroup(sp, name)) {
1389       SITEmark(sp, ngp);
1390     }
1391   }
1392 }
1393
1394 /*
1395 **  Check if site doesn't want this group even if it's crossposted
1396 **  to a wanted group.
1397 */
1398 static void
1399 ARTpoisongroup(char *name)
1400 {
1401   SITE  *sp;
1402   int   i;
1403
1404   for (sp = Sites, i = nSites; --i >= 0; sp++) {
1405     if (sp->Name != NULL && (sp->PoisonEntry || ME.PoisonEntry) &&
1406       SITEpoisongroup(sp, name))
1407       sp->Poison = true;
1408   }
1409 }
1410
1411 /*
1412 ** Assign article numbers to the article and create the Xref line.
1413 ** If we end up not being able to write the article, we'll get "holes"
1414 ** in the directory and active file.
1415 */
1416 static void
1417 ARTassignnumbers(ARTDATA *data)
1418 {
1419   char          *p, *q;
1420   int           i, len, linelen, buflen;
1421   NEWSGROUP     *ngp;
1422
1423   if (data->XrefBufLength == 0) {
1424     data->XrefBufLength = MAXHEADERSIZE * 2 + 1;
1425     data->Xref = xmalloc(data->XrefBufLength);
1426     strncpy(data->Xref, Path.data, Path.used - 1);
1427   }
1428   len = Path.used - 1;
1429   p = q = data->Xref + len;
1430   for (linelen = i = 0; (ngp = GroupPointers[i]) != NULL; i++) {
1431     /* If already went to this group (i.e., multiple groups are aliased
1432      * into it), then skip it. */
1433     if (ngp->PostCount > 0)
1434       continue;
1435
1436     /* Bump the number. */
1437     ngp->PostCount++;
1438     ngp->Last++;
1439     if (!FormatLong(ngp->LastString, (long)ngp->Last, ngp->Lastwidth)) {
1440       syslog(L_ERROR, "%s cant update_active %s", LogName, ngp->Name);
1441       continue;
1442     }
1443     ngp->Filenum = ngp->Last;
1444     /*  len  ' ' "news_groupname"  ':' "#" "\r\n" */
1445     if (len + 1 + ngp->NameLength + 1 + 10 + 2 > data->XrefBufLength) {
1446       data->XrefBufLength += MAXHEADERSIZE;
1447       data->Xref = xrealloc(data->Xref, data->XrefBufLength);
1448       p = data->Xref + len;
1449     }
1450     if (linelen + 1 + ngp->NameLength + 1 + 10 > MAXHEADERSIZE) {
1451       /* line exceeded */
1452       sprintf(p, "\r\n %s:%lu", ngp->Name, ngp->Filenum);
1453       buflen = strlen(p);
1454       linelen = buflen - 2;
1455     } else {
1456       sprintf(p, " %s:%lu", ngp->Name, ngp->Filenum);
1457       buflen = strlen(p);
1458       linelen += buflen;
1459     }
1460     len += buflen;
1461     p += buflen;
1462   }
1463   /* p[0] is replaced with '\r' to be wireformatted when stored.  p[1] needs to
1464      be '\n' */
1465   p[0] = '\r';
1466   p[1] = '\n';
1467   /* data->XrefLength includes trailing "\r\n" */
1468   data->XrefLength = len + 2;
1469   data->Replic = q + 1;
1470   data->ReplicLength = len - (q + 1 - data->Xref);
1471 }
1472
1473 /*
1474 **  Parse the data from the xref header and assign the numbers.
1475 **  This involves replacing the GroupPointers entries.
1476 */
1477 static bool
1478 ARTxrefslave(ARTDATA *data)
1479 {
1480   char          *p, *q, *name, *next, c = 0;
1481   NEWSGROUP     *ngp;
1482   int           i;
1483   bool          nogroup = true;
1484   HDRCONTENT    *hc = data->HdrContent;
1485
1486   if (!HDR_FOUND(HDR__XREF))
1487     return false;
1488   /* skip server name */
1489   if ((p = strpbrk(HDR(HDR__XREF), " \t\r\n")) == NULL)
1490     return false;
1491   /* in case Xref is folded */
1492   while (*++p == ' ' || *p == '\t' || *p == '\r' || *p == '\n');
1493   if (*p == '\0')
1494     return false;
1495   data->Replic = p;
1496   data->ReplicLength = HDR_LEN(HDR__XREF) - (p - HDR(HDR__XREF));
1497   for (i = 0; (*p != '\0') && (p < HDR(HDR__XREF) + HDR_LEN(HDR__XREF)) ; p = next) {
1498     /* Mark end of this entry and where next one starts. */
1499     name = p;
1500     if ((q = next = strpbrk(p, " \t\r\n")) != NULL) {
1501       c = *q;
1502       *q = '\0';
1503       while (*++next == ' ' || *next == '\t' || *next == '\r' || *next == '\n');
1504     } else {
1505       q = NULL;
1506       next = "";
1507     }
1508
1509     /* Split into news.group:# */
1510     if ((p = strchr(p, ':')) == NULL) {
1511       syslog(L_ERROR, "%s bad_format %s", LogName, name);
1512       if (q != NULL)
1513         *q = c;
1514       continue;
1515     }
1516     *p = '\0';
1517     if ((ngp = NGfind(name)) == NULL) {
1518       syslog(L_ERROR, "%s bad_newsgroup %s", LogName, name);
1519       *p = ':';
1520       if (q != NULL)
1521         *q = c;
1522       continue;
1523     }
1524     *p = ':';
1525     ngp->Filenum = atol(p + 1);
1526     if (q != NULL)
1527       *q = c;
1528
1529     /* Update active file if we got a new high-water mark. */
1530     if (ngp->Last < ngp->Filenum) {
1531       ngp->Last = ngp->Filenum;
1532       if (!FormatLong(ngp->LastString, (long)ngp->Last, ngp->Lastwidth)) {
1533         syslog(L_ERROR, "%s cant update_active %s", LogName, ngp->Name);
1534         continue;
1535       }
1536     }
1537     /* Mark that this group gets the article. */
1538     ngp->PostCount++;
1539     GroupPointers[i++] = ngp;
1540     nogroup = false;
1541   }
1542   GroupPointers[i] = NULL;
1543   if (nogroup)
1544     return false;
1545   return true;
1546 }
1547
1548 /*
1549 **  Return true if a list of strings has a specific one.  This is a
1550 **  generic routine, but is used for seeing if a host is in the Path line.
1551 */
1552 static bool
1553 ListHas(const char **list, const char *p)
1554 {
1555   const char    *q;
1556   char          c;
1557
1558   for (c = *p; (q = *list) != NULL; list++)
1559     if (strcasecmp(p, q) == 0)
1560       return true;
1561   return false;
1562 }
1563
1564 /*
1565 **  Even though we have already calculated the Message-ID MD5sum,
1566 **  we have to do it again since unfortunately HashMessageID()
1567 **  lowercases the Message-ID first.  We also need to remain
1568 **  compatible with Diablo's hashfeed.
1569 */
1570
1571 static unsigned int
1572 HashFeedMD5(char *MessageID, unsigned int offset)
1573 {
1574   static char LastMessageID[128];
1575   static char *LastMessageIDPtr;
1576   static struct md5_context context;
1577   unsigned int ret;
1578
1579   if (offset > 12)
1580     return 0;
1581
1582   /* Some light caching. */
1583   if (MessageID != LastMessageIDPtr ||
1584     strcmp(MessageID, LastMessageID) != 0) {
1585     md5_init(&context);
1586     md5_update(&context, (unsigned char *)MessageID, strlen(MessageID));
1587     md5_final(&context);
1588     LastMessageIDPtr = MessageID;
1589     strncpy(LastMessageID, MessageID, sizeof(LastMessageID) - 1);
1590     LastMessageID[sizeof(LastMessageID) - 1] = 0;
1591   }
1592
1593   memcpy(&ret, &context.digest[12 - offset], 4);
1594
1595   return ntohl(ret);
1596 }
1597
1598 /*
1599 ** Old-style Diablo (< 5.1) quickhash.
1600 **
1601 */
1602 static unsigned int
1603 HashFeedQH(char *MessageID, unsigned int *tmp)
1604 {
1605   unsigned char *p;
1606   int n;
1607
1608   if (*tmp != (unsigned int)-1)
1609     return *tmp;
1610
1611   p = (unsigned char *)MessageID;
1612   n = 0;
1613   while (*p)
1614     n += *p++;
1615   *tmp = (unsigned int)n;
1616
1617   return *tmp;
1618 }
1619
1620 /*
1621 **  Return true if an element of the HASHFEEDLIST matches
1622 **  the hash of the Message-ID.
1623 */
1624 static bool
1625 HashFeedMatch(HASHFEEDLIST *hf, char *MessageID)
1626 {
1627   unsigned int qh = (unsigned int)-1;
1628   unsigned int h;
1629
1630   while (hf) {
1631     if (hf->type == HASHFEED_MD5)
1632       h = HashFeedMD5(MessageID, hf->offset);
1633     else if (hf->type == HASHFEED_QH)
1634       h = HashFeedQH(MessageID, &qh);
1635     else
1636       continue;
1637     if ((h % hf->mod + 1) >= hf->begin &&
1638         (h % hf->mod + 1) <= hf->end)
1639           return true;
1640     hf = hf->next;
1641   }
1642
1643   return false;
1644 }
1645
1646 /*
1647 **  Propagate an article to the sites have "expressed an interest."
1648 */
1649 static void
1650 ARTpropagate(ARTDATA *data, const char **hops, int hopcount, char **list,
1651   bool ControlStore, bool OverviewCreated)
1652 {
1653   HDRCONTENT    *hc = data->HdrContent;
1654   SITE          *sp, *funnel;
1655   int           i, j, Groupcount, Followcount, Crosscount;
1656   char          *p, *q;
1657   struct buffer *bp;
1658   bool          sendit;
1659
1660   /* Work out which sites should really get it. */
1661   Groupcount = data->Groupcount;
1662   Followcount = data->Followcount;
1663   Crosscount = Groupcount + Followcount * Followcount;
1664   for (sp = Sites, i = nSites; --i >= 0; sp++) {
1665     if ((sp->IgnoreControl && ControlStore) ||
1666       (sp->NeedOverviewCreation && !OverviewCreated))
1667       sp->Sendit = false;
1668     if (sp->Seenit || !sp->Sendit)
1669       continue;
1670     sp->Sendit = false;
1671         
1672     if (sp->Originator) {
1673       if (!HDR_FOUND(HDR__XTRACE)) {
1674         if (!sp->FeedwithoutOriginator)
1675           continue;
1676       } else {
1677         if ((p = strchr(HDR(HDR__XTRACE), ' ')) != NULL) {
1678           *p = '\0';
1679           for (j = 0, sendit = false; (q = sp->Originator[j]) != NULL; j++) {
1680             if (*q == '@') {
1681               if (uwildmat(HDR(HDR__XTRACE), &q[1])) {
1682                 *p = ' ';
1683                 sendit = false;
1684                 break;
1685               }
1686             } else {
1687               if (uwildmat(HDR(HDR__XTRACE), q))
1688                 sendit = true;
1689             }
1690           }
1691           *p = ' ';
1692           if (!sendit)
1693             continue;
1694         } else
1695           continue;
1696       }
1697     }
1698
1699     if (sp->Master != NOSITE && Sites[sp->Master].Seenit)
1700       continue;
1701
1702     if (sp->MaxSize && data->BytesValue > sp->MaxSize)
1703       /* Too big for the site. */
1704       continue;
1705
1706     if (sp->MinSize && data->BytesValue < sp->MinSize)
1707       /* Too small for the site. */
1708       continue;
1709
1710     if ((sp->Hops && hopcount > sp->Hops)
1711       || (!sp->IgnorePath && ListHas(hops, sp->Name))
1712       || (sp->Groupcount && Groupcount > sp->Groupcount)
1713       || (sp->Followcount && Followcount > sp->Followcount)
1714       || (sp->Crosscount && Crosscount > sp->Crosscount))
1715       /* Site already saw the article; path too long; or too much
1716        * cross-posting. */
1717       continue;
1718
1719     if (sp->HashFeedList &&
1720       !HashFeedMatch(sp->HashFeedList, HDR(HDR__MESSAGE_ID)))
1721       /* hashfeed doesn't match */
1722       continue;
1723
1724     if (list && *list != NULL && sp->Distributions &&
1725       !DISTwantany(sp->Distributions, list))
1726       /* Not in the site's desired list of distributions. */
1727       continue;
1728     if (sp->DistRequired && (list == NULL || *list == NULL))
1729       /* Site requires Distribution header and there isn't one. */
1730       continue;
1731
1732     if (sp->Exclusions) {
1733       for (j = 0; (p = sp->Exclusions[j]) != NULL; j++)
1734         if (ListHas(hops, p))
1735           break;
1736       if (p != NULL)
1737         /* A host in the site's exclusion list was in the Path. */
1738         continue;
1739     }
1740
1741     /* Write that the site is getting it, and flag to send it. */
1742     if (innconf->logsitename) {
1743       if (fprintf(Log, " %s", sp->Name) == EOF || ferror(Log)) {
1744         j = errno;
1745         syslog(L_ERROR, "%s cant write log_site %m", LogName);
1746         IOError("logging site", j);
1747         clearerr(Log);
1748       }
1749     }
1750     sp->Sendit = true;
1751     sp->Seenit = true;
1752     if (sp->Master != NOSITE)
1753       Sites[sp->Master].Seenit = true;
1754   }
1755   if (putc('\n', Log) == EOF
1756     || (!BufferedLogs && fflush(Log))
1757     || ferror(Log)) {
1758     syslog(L_ERROR, "%s cant write log_end %m", LogName);
1759     clearerr(Log);
1760   }
1761
1762   /* Handle funnel sites. */
1763   for (sp = Sites, i = nSites; --i >= 0; sp++) {
1764     if (sp->Sendit && sp->Funnel != NOSITE) {
1765       sp->Sendit = false;
1766       funnel = &Sites[sp->Funnel];
1767       funnel->Sendit = true;
1768       if (funnel->FNLwantsnames) {
1769         bp = &funnel->FNLnames;
1770         p = &bp->data[bp->used];
1771         if (bp->used) {
1772           *p++ = ' ';
1773           bp->used++;
1774         }
1775         bp->used += strlcpy(p, sp->Name, bp->size - bp->used);
1776       }
1777     }
1778   }
1779 }
1780
1781 /*
1782 **  Build up the overview data.
1783 */
1784 static void
1785 ARTmakeoverview(CHANNEL *cp)
1786 {
1787   ARTDATA       *data = &cp->Data;
1788   HDRCONTENT    *hc = data->HdrContent;
1789   static char   SEP[] = "\t";
1790   static char   COLONSPACE[] = ": ";
1791   struct buffer *overview = &data->Overview;
1792   ARTOVERFIELD  *fp;
1793   const ARTHEADER *hp;
1794   char          *p, *q;
1795   int           i, j, len;
1796   char          *key_old_value = NULL;
1797   int           key_old_length = 0;
1798
1799   if (ARTfields == NULL) {
1800     /* User error. */
1801     return;
1802   }
1803
1804   /* Setup. */
1805   buffer_resize(overview, MAXHEADERSIZE);
1806   buffer_set(overview, "", 0);
1807
1808   /* Write the data, a field at a time. */
1809   for (fp = ARTfields; fp->Header; fp++) {
1810     if (fp != ARTfields)
1811       buffer_append(overview, SEP, strlen(SEP));
1812     hp = fp->Header;
1813     j = hp - ARTheaders;
1814
1815     /* If requested, generate keywords from the body of the article and patch
1816        them into the apparent value of the Keywords header so that they make
1817        it into overview. */
1818     if (DO_KEYWORDS && innconf->keywords) {
1819       /* Ensure that there are Keywords: to shovel. */
1820       if (hp == &ARTheaders[HDR__KEYWORDS]) {
1821         key_old_value  = HDR(HDR__KEYWORDS);
1822         key_old_length = HDR_LEN(HDR__KEYWORDS);
1823         KEYgenerate(&hc[HDR__KEYWORDS], cp->In.data + data->Body,
1824                     key_old_value, key_old_length);
1825       }
1826     }
1827
1828     switch (j) {
1829       case HDR__BYTES:
1830         p = data->Bytes + 7; /* skip "Bytes: " */
1831         len = data->BytesLength;
1832         break;
1833       case HDR__XREF:
1834         if (innconf->xrefslave) {
1835           p = HDR(j);
1836           len = HDR_LEN(j);
1837         } else {
1838           p = data->Xref;
1839           len = data->XrefLength - 2;
1840         }
1841         break;
1842       default:
1843         p = HDR(j);
1844         len = HDR_LEN(j);
1845         break;
1846     }
1847     if (len == 0)
1848       continue;
1849     if (fp->NeedHeader) {
1850       buffer_append(overview, hp->Name, hp->Size);
1851       buffer_append(overview, COLONSPACE, strlen(COLONSPACE));
1852     }
1853     if (overview->used + overview->left + len > overview->size)
1854         buffer_resize(overview, overview->size + len);
1855     for (i = 0, q = overview->data + overview->left; i < len; p++, i++) {
1856         if (*p == '\r' && i < len - 1 && p[1] == '\n') {
1857             p++;
1858             i++;
1859             continue;
1860         }
1861         if (*p == '\0' || *p == '\t' || *p == '\n' || *p == '\r')
1862             *q++ = ' ';
1863         else
1864             *q++ = *p;
1865         overview->left++;
1866     }
1867
1868     /* Patch the old keywords back in. */
1869     if (DO_KEYWORDS && innconf->keywords) {
1870       if (key_old_value) {
1871         if (hc->Value)
1872           free(hc->Value);              /* malloc'd within */
1873         hc->Value  = key_old_value;
1874         hc->Length = key_old_length;
1875         key_old_value = NULL;
1876       }
1877     }
1878   }
1879 }
1880
1881 /*
1882 **  This routine is the heart of it all.  Take a full article, parse it,
1883 **  file or reject it, feed it to the other sites.  Return the NNTP
1884 **  message to send back.
1885 */
1886 bool
1887 ARTpost(CHANNEL *cp)
1888 {
1889   char          *p, **groups, ControlWord[SMBUF], **hops, *controlgroup;
1890   int           i, j, *isp, hopcount, oerrno, canpost;
1891   NEWSGROUP     *ngp, **ngptr;
1892   SITE          *sp;
1893   ARTDATA       *data = &cp->Data;
1894   HDRCONTENT    *hc = data->HdrContent;
1895   bool          Approved, Accepted, LikeNewgroup, ToGroup, GroupMissing;
1896   bool          NoHistoryUpdate, artclean;
1897   bool          ControlStore = false;
1898   bool          NonExist = false;
1899   bool          OverviewCreated = false;
1900   bool          IsControl = false;
1901   bool          Filtered = false;
1902   struct buffer *article;
1903   HASH          hash;
1904   TOKEN         token;
1905   char          *groupbuff[2];
1906 #if defined(DO_PERL) || defined(DO_PYTHON)
1907   char          *filterrc;
1908 #endif /* defined(DO_PERL) || defined(DO_PYTHON) */
1909   OVADDRESULT   result;
1910
1911   /* Preliminary clean-ups. */
1912   article = &cp->In;
1913   artclean = ARTclean(data, cp->Error);
1914
1915   /* If we don't have Path or Message-ID, we can't continue. */
1916   if (!artclean && (!HDR_FOUND(HDR__PATH) || !HDR_FOUND(HDR__MESSAGE_ID)))
1917     return false;
1918   hopcount = ARTparsepath(HDR(HDR__PATH), HDR_LEN(HDR__PATH), &data->Path);
1919   if (hopcount == 0) {
1920     snprintf(cp->Error, sizeof(cp->Error), "%d illegal path element",
1921             NNTP_REJECTIT_VAL);
1922     return false;
1923   }
1924   hops = data->Path.List;
1925
1926   if (innconf->logipaddr) {
1927     data->Feedsite = RChostname(cp);
1928     if (data->Feedsite == NULL)
1929       data->Feedsite = CHANname(cp);
1930     if (strcmp("0.0.0.0", data->Feedsite) == 0 || data->Feedsite[0] == '\0')
1931       data->Feedsite = hops && hops[0] ? hops[0] : CHANname(cp);
1932   } else {
1933     data->Feedsite = hops && hops[0] ? hops[0] : CHANname(cp);
1934   }
1935   data->FeedsiteLength = strlen(data->Feedsite);
1936
1937   hash = HashMessageID(HDR(HDR__MESSAGE_ID));
1938   data->Hash = &hash;
1939   if (HIScheck(History, HDR(HDR__MESSAGE_ID))) {
1940     snprintf(cp->Error, sizeof(cp->Error), "%d Duplicate", NNTP_REJECTIT_VAL);
1941     ARTlog(data, ART_REJECT, cp->Error);
1942     ARTreject(REJECT_DUPLICATE, cp, article);
1943     return false;
1944   }
1945   if (!artclean) {
1946     ARTlog(data, ART_REJECT, cp->Error);
1947     if (innconf->remembertrash && (Mode == OMrunning) &&
1948         !InndHisRemember(HDR(HDR__MESSAGE_ID)))
1949       syslog(L_ERROR, "%s cant write history %s %m", LogName,
1950         HDR(HDR__MESSAGE_ID));
1951     ARTreject(REJECT_OTHER, cp, article);
1952     return false;
1953   }
1954
1955   i = strlen(hops[0]);
1956   if (i == Path.used - 1 &&
1957     strncmp(Path.data, hops[0], Path.used - 1) == 0)
1958     data->Hassamepath = true;
1959   else
1960     data->Hassamepath = false;
1961   if (Pathcluster.data != NULL &&
1962     i == Pathcluster.used - 1 &&
1963     strncmp(Pathcluster.data, hops[0], Pathcluster.used - 1) == 0)
1964     data->Hassamecluster = true;
1965   else
1966     data->Hassamecluster = false;
1967   if (Pathalias.data != NULL &&
1968     !ListHas((const char **)hops, (const char *)innconf->pathalias))
1969     data->AddAlias = true;
1970   else
1971     data->AddAlias = false;
1972
1973   /* And now check the path for unwanted sites -- Andy */
1974   for(j = 0 ; ME.Exclusions && ME.Exclusions[j] ; j++) {
1975     if (ListHas((const char **)hops, (const char *)ME.Exclusions[j])) {
1976       snprintf(cp->Error, sizeof(cp->Error), "%d Unwanted site %s in path",
1977         NNTP_REJECTIT_VAL, MaxLength(ME.Exclusions[j], ME.Exclusions[j]));
1978       ARTlog(data, ART_REJECT, cp->Error);
1979       if (innconf->remembertrash && (Mode == OMrunning) &&
1980           !InndHisRemember(HDR(HDR__MESSAGE_ID)))
1981         syslog(L_ERROR, "%s cant write history %s %m", LogName,
1982           HDR(HDR__MESSAGE_ID));
1983       ARTreject(REJECT_SITE, cp, article);
1984       return false;
1985     }
1986   }
1987
1988 #if defined(DO_PERL) || defined(DO_PYTHON)
1989   filterPath = HDR(HDR__PATH);
1990 #endif /* DO_PERL || DO_PYHTON */
1991
1992 #if defined(DO_PYTHON)
1993   TMRstart(TMR_PYTHON);
1994   filterrc = PYartfilter(data, article->data + data->Body,
1995     cp->Next - data->Body, data->Lines);
1996   TMRstop(TMR_PYTHON);
1997   if (filterrc != NULL) {
1998     if (innconf->dontrejectfiltered) {
1999       Filtered = true;
2000     } else {
2001       snprintf(cp->Error, sizeof(cp->Error), "%d %.200s", NNTP_REJECTIT_VAL,
2002                filterrc);
2003       syslog(L_NOTICE, "rejecting[python] %s %s", HDR(HDR__MESSAGE_ID),
2004              cp->Error);
2005       ARTlog(data, ART_REJECT, cp->Error);
2006       if (innconf->remembertrash && (Mode == OMrunning) &&
2007           !InndHisRemember(HDR(HDR__MESSAGE_ID)))
2008         syslog(L_ERROR, "%s cant write history %s %m", LogName,
2009           HDR(HDR__MESSAGE_ID));
2010       ARTreject(REJECT_FILTER, cp, article);
2011       return false;
2012     }
2013   }
2014 #endif /* DO_PYTHON */
2015
2016   /* I suppose some masochist will run with Python and Perl in together */
2017
2018 #if defined(DO_PERL)
2019   TMRstart(TMR_PERL);
2020   filterrc = PLartfilter(data, article->data + data->Body,
2021     cp->Next - data->Body, data->Lines);
2022   TMRstop(TMR_PERL);
2023   if (filterrc) {
2024     if (innconf->dontrejectfiltered) {
2025       Filtered = true;
2026     } else {
2027       snprintf(cp->Error, sizeof(cp->Error), "%d %.200s", NNTP_REJECTIT_VAL,
2028                filterrc);
2029       syslog(L_NOTICE, "rejecting[perl] %s %s", HDR(HDR__MESSAGE_ID),
2030              cp->Error);
2031       ARTlog(data, ART_REJECT, cp->Error);
2032       if (innconf->remembertrash && (Mode == OMrunning) &&
2033           !InndHisRemember(HDR(HDR__MESSAGE_ID)))
2034         syslog(L_ERROR, "%s cant write history %s %m", LogName,
2035           HDR(HDR__MESSAGE_ID));
2036       ARTreject(REJECT_FILTER, cp, article);
2037       return false;
2038     }
2039   }
2040 #endif /* DO_PERL */
2041
2042   /* I suppose some masochist will run with both TCL and Perl in together */
2043
2044 #if defined(DO_TCL)
2045   if (TCLFilterActive) {
2046     int code;
2047     const ARTHEADER *hp;
2048
2049     /* make info available to Tcl */
2050
2051     TCLCurrArticle = article;
2052     TCLCurrData = data;
2053     Tcl_UnsetVar(TCLInterpreter, "Body", TCL_GLOBAL_ONLY);
2054     Tcl_UnsetVar(TCLInterpreter, "Headers", TCL_GLOBAL_ONLY);
2055     for (i = 0 ; i < MAX_ARTHEADER ; i++, hc++) {
2056       if (HDR_FOUND(i)) {
2057         hp = &ARTheaders[i];
2058         Tcl_SetVar2(TCLInterpreter, "Headers", (char *) hp->Name, HDR(i),
2059           TCL_GLOBAL_ONLY);
2060       }
2061     }
2062     Tcl_SetVar(TCLInterpreter, "Body", article->data + data->Body,
2063       TCL_GLOBAL_ONLY);
2064     /* call filter */
2065
2066     code = Tcl_Eval(TCLInterpreter, "filter_news");
2067     Tcl_UnsetVar(TCLInterpreter, "Body", TCL_GLOBAL_ONLY);
2068     Tcl_UnsetVar(TCLInterpreter, "Headers", TCL_GLOBAL_ONLY);
2069     if (code == TCL_OK) {
2070       if (strcmp(TCLInterpreter->result, "accept") != 0) {
2071         if (innconf->dontrejectfiltered) {
2072           Filtered = true;
2073         } else {
2074           snprintf(cp->Error, sizeof(cp->Error), "%d %.200s",
2075                    NNTP_REJECTIT_VAL, TCLInterpreter->result);
2076           syslog(L_NOTICE, "rejecting[tcl] %s %s", HDR(HDR__MESSAGE_ID),
2077                  cp->Error);
2078           ARTlog(data, ART_REJECT, cp->Error);
2079           if (innconf->remembertrash && (Mode == OMrunning) &&
2080               !InndHisRemember(HDR(HDR__MESSAGE_ID)))
2081             syslog(L_ERROR, "%s cant write history %s %m",
2082               LogName, HDR(HDR__MESSAGE_ID));
2083           ARTreject(REJECT_FILTER, cp, article);
2084           return false;
2085         }
2086       }
2087     } else {
2088       /* the filter failed: complain and then turn off filtering */
2089       syslog(L_ERROR, "TCL proc filter_news failed: %s",
2090         TCLInterpreter->result);
2091       TCLfilter(false);
2092     }
2093   }
2094 #endif /* defined(DO_TCL) */
2095
2096   /* If we limit what distributions we get, see if we want this one. */
2097   if (HDR_FOUND(HDR__DISTRIBUTION)) {
2098     if (HDR(HDR__DISTRIBUTION)[0] == ',') {
2099       snprintf(cp->Error, sizeof(cp->Error), "%d bogus distribution \"%s\"",
2100                NNTP_REJECTIT_VAL,
2101                MaxLength(HDR(HDR__DISTRIBUTION), HDR(HDR__DISTRIBUTION)));
2102       ARTlog(data, ART_REJECT, cp->Error);
2103       if (innconf->remembertrash && Mode == OMrunning &&
2104           !InndHisRemember(HDR(HDR__MESSAGE_ID)))
2105         syslog(L_ERROR, "%s cant write history %s %m", LogName,
2106           HDR(HDR__MESSAGE_ID));
2107       ARTreject(REJECT_DISTRIB, cp, article);
2108       return false;
2109     } else {
2110       ARTparsedist(HDR(HDR__DISTRIBUTION), HDR_LEN(HDR__DISTRIBUTION),
2111         &data->Distribution);
2112       if (ME.Distributions &&
2113         !DISTwantany(ME.Distributions, data->Distribution.List)) {
2114         snprintf(cp->Error, sizeof(cp->Error),
2115                  "%d Unwanted distribution \"%s\"", NNTP_REJECTIT_VAL,
2116                  MaxLength(data->Distribution.List[0],
2117                            data->Distribution.List[0]));
2118         ARTlog(data, ART_REJECT, cp->Error);
2119         if (innconf->remembertrash && (Mode == OMrunning) &&
2120             !InndHisRemember(HDR(HDR__MESSAGE_ID)))
2121           syslog(L_ERROR, "%s cant write history %s %m",
2122             LogName, HDR(HDR__MESSAGE_ID));
2123         ARTreject(REJECT_DISTRIB, cp, article);
2124         return false;
2125       }
2126     }
2127   } else {
2128     ARTparsedist("", 0, &data->Distribution);
2129   }
2130
2131   for (i = nSites, sp = Sites; --i >= 0; sp++) {
2132     sp->Poison = false;
2133     sp->Sendit = false;
2134     sp->Seenit = false;
2135     sp->FNLnames.used = 0;
2136     sp->ng = NULL;
2137   }
2138
2139   if (HDR_FOUND(HDR__FOLLOWUPTO)) {
2140     for (i = 0, p = HDR(HDR__FOLLOWUPTO) ; (p = strchr(p, ',')) != NULL ;
2141       i++, p++)
2142       continue;
2143     data->Followcount = i;
2144   }
2145   if (data->Followcount == 0)
2146     data->Followcount = data->Groupcount;
2147
2148   groups = data->Newsgroups.List;
2149   /* Parse the Control header. */
2150   LikeNewgroup = false;
2151   if (HDR_FOUND(HDR__CONTROL)) {
2152     IsControl = true;
2153
2154     /* Nip off the first word into lowercase. */
2155     strlcpy(ControlWord, HDR(HDR__CONTROL), sizeof(ControlWord));
2156     for (p = ControlWord; *p && !ISWHITE(*p); p++)
2157       if (CTYPE(isupper, *p))
2158         *p = tolower(*p);
2159     *p = '\0';
2160     LikeNewgroup = (strcmp(ControlWord, "newgroup") == 0
2161                     || strcmp(ControlWord, "rmgroup") == 0);
2162
2163     if (innconf->ignorenewsgroups && LikeNewgroup) {
2164       for (p++; *p && ISWHITE(*p); p++);
2165       groupbuff[0] = p;
2166       for (p++; *p; p++) {
2167         if (NG_ISSEP(*p)) {
2168           *p = '\0';
2169           break;
2170         }
2171       }
2172       p = groupbuff[0];
2173       for (p++; *p; p++) {
2174         if (ISWHITE(*p)) {
2175           *p = '\0';
2176           break;
2177         }
2178       }
2179       groupbuff[1] = NULL;
2180       groups = groupbuff;
2181       data->Groupcount = 2;
2182       if (data->Followcount == 0)
2183         data->Followcount = data->Groupcount;
2184     }
2185     
2186     LikeNewgroup = (LikeNewgroup || strcmp(ControlWord, "checkgroups") == 0);
2187     
2188     /* Control messages to "foo.ctl" are treated as if they were
2189      * posted to "foo".  I should probably apologize for all the
2190      * side-effects in the if. */
2191     for (i = 0; (p = groups[i++]) != NULL; )
2192       if ((j = strlen(p) - 4) > 0 && *(p += j) == '.'
2193         && p[1] == 'c' && p[2] == 't' && p[3] == 'l')
2194           *p = '\0';
2195   }
2196
2197   /* Loop over the newsgroups, see which ones we want, and get the
2198    * total space needed for the Xref line.  At the end of this section
2199    * of code, j will have the needed length, the appropriate site
2200    * entries will have their Sendit and ng fields set, and GroupPointers
2201    * will have pointers to the relevant newsgroups. */
2202   ToGroup = NoHistoryUpdate = false;
2203   Approved = HDR_FOUND(HDR__APPROVED);
2204   ngptr = GroupPointers;
2205   for (GroupMissing = Accepted = false; (p = *groups) != NULL; groups++) {
2206     if ((ngp = NGfind(p)) == NULL) {
2207       GroupMissing = true;
2208       if (LikeNewgroup && Approved) {
2209         /* Checkgroups/newgroup/rmgroup being sent to a group that doesn't
2210          * exist.  Assume it is being sent to the group being created or
2211          * removed (or to the admin group to which the checkgroups is posted),
2212          * and send it to all sites that would or would have had the group
2213          * if it were created. */
2214         ARTsendthegroup(*groups);
2215         Accepted = true;
2216       } else
2217         NonExist = true;
2218       ARTpoisongroup(*groups);
2219
2220       if (innconf->mergetogroups) {
2221         /* Try to collapse all "to" newsgroups. */
2222         if (*p != 't' || *++p != 'o' || *++p != '.' || *++p == '\0')
2223           continue;
2224         ngp = NGfind("to");
2225         ToGroup = true;
2226         if ((sp = SITEfind(p)) != NULL) {
2227           SITEmark(sp, ngp);
2228         }
2229       } else {
2230         continue;
2231       }
2232     }
2233         
2234     ngp->PostCount = 0;
2235     /* Ignore this group? */
2236     if (ngp->Rest[0] == NF_FLAG_IGNORE) {
2237       /* See if any of this group's sites considers this group poison. */
2238       for (isp = ngp->Poison, i = ngp->nPoison; --i >= 0; isp++)
2239         if (*isp >= 0)
2240           Sites[*isp].Poison = true;
2241       continue;
2242     }
2243
2244     /* Basic validity check. */
2245     if (ngp->Rest[0] == NF_FLAG_MODERATED && !Approved) {
2246       snprintf(cp->Error, sizeof(cp->Error), "%d Unapproved for \"%s\"",
2247                NNTP_REJECTIT_VAL, MaxLength(ngp->Name, ngp->Name));
2248       ARTlog(data, ART_REJECT, cp->Error);
2249       if (innconf->remembertrash && (Mode == OMrunning) &&
2250           !InndHisRemember(HDR(HDR__MESSAGE_ID)))
2251         syslog(L_ERROR, "%s cant write history %s %m", LogName,
2252           HDR(HDR__MESSAGE_ID));
2253       ARTreject(REJECT_UNAPP, cp, article);
2254       return false;
2255     }
2256
2257     /* See if any of this group's sites considers this group poison. */
2258     for (isp = ngp->Poison, i = ngp->nPoison; --i >= 0; isp++)
2259       if (*isp >= 0)
2260         Sites[*isp].Poison = true;
2261
2262     /* Check if we accept articles in this group from this peer, after
2263        poisoning.  This means that articles that we accept from them will
2264        be handled correctly if they're crossposted. */
2265     canpost = RCcanpost(cp, p);
2266     if (!canpost) {  /* At least one group cannot be fed by this peer.
2267                         If we later reject the post as unwanted group,
2268                         don't remember it.  If we accept, do remember */
2269       NoHistoryUpdate = true;
2270       continue;
2271     } else if (canpost < 0) {
2272       snprintf(cp->Error, sizeof(cp->Error),
2273                "%d Won't accept posts in \"%s\"", NNTP_REJECTIT_VAL,
2274                MaxLength(p, p));
2275       ARTlog(data, ART_REJECT, cp->Error);
2276       ARTreject(REJECT_GROUP, cp, article);
2277       return false;
2278     }
2279
2280     /* Valid group, feed it to that group's sites. */
2281     Accepted = true;
2282     for (isp = ngp->Sites, i = ngp->nSites; --i >= 0; isp++) {
2283       if (*isp >= 0) {
2284         sp = &Sites[*isp];
2285         if (!sp->Poison)
2286           SITEmark(sp, ngp);
2287       }
2288     }
2289
2290     /* If it's excluded, don't file it. */
2291     if (ngp->Rest[0] == NF_FLAG_EXCLUDED)
2292       continue;
2293
2294     /* Expand aliases, mark the article as getting filed in the group. */
2295     if (ngp->Alias != NULL)
2296       ngp = ngp->Alias;
2297     *ngptr++ = ngp;
2298     ngp->PostCount = 0;
2299   }
2300
2301   /* Loop over sites to find Poisons/ControlOnly and undo Sendit flags. */
2302   for (i = nSites, sp = Sites; --i >= 0; sp++) {
2303     if (sp->Poison || (sp->ControlOnly && !IsControl)
2304       || (sp->DontWantNonExist && NonExist))
2305       sp->Sendit = false;               
2306   }
2307
2308   /* Control messages not filed in "to" get filed only in control.name
2309    * or control. */
2310   if (IsControl && Accepted && !ToGroup) {
2311     ControlStore = true;
2312     controlgroup = concat("control.", ControlWord, (char *) 0);
2313     if ((ngp = NGfind(controlgroup)) == NULL)
2314       ngp = NGfind(ARTctl);
2315     free(controlgroup);
2316     ngp->PostCount = 0;
2317     ngptr = GroupPointers;
2318     *ngptr++ = ngp;
2319     for (isp = ngp->Sites, i = ngp->nSites; --i >= 0; isp++) {
2320       if (*isp >= 0) {
2321         /* Checkgroups/newgroup/rmgroup posted to local.example
2322          * will still be sent with the newsfeeds patterns
2323          * "*,!local.*" and "*,@local.*".  So as not to propagate
2324          * them, "!control,!control.*" should be added. */
2325         sp = &Sites[*isp];
2326         SITEmark(sp, ngp);
2327       }
2328     }
2329   }
2330
2331   /* If !Accepted, then none of the article's newgroups exist in our
2332    * active file.  Proper action is to drop the article on the floor.
2333    * If ngp == GroupPointers, then all the new articles newsgroups are
2334    * "j" entries in the active file.  In that case, we have to file it
2335    * under junk so that downstream feeds can get it. */
2336   if (!Accepted || ngptr == GroupPointers) {
2337     if (!Accepted) {
2338       if (NoHistoryUpdate) {
2339         snprintf(cp->Error, sizeof(cp->Error), "%d Can't post to \"%s\"",
2340                 NNTP_REJECTIT_VAL, MaxLength(data->Newsgroups.List[0],
2341                                              data->Newsgroups.List[0]));
2342       } else {
2343         snprintf(cp->Error, sizeof(cp->Error),
2344                  "%d Unwanted newsgroup \"%s\"", NNTP_REJECTIT_VAL,
2345                  MaxLength(data->Newsgroups.List[0],
2346                            data->Newsgroups.List[0]));
2347       }
2348       ARTlog(data, ART_REJECT, cp->Error);
2349       if (!innconf->wanttrash) {
2350         if (innconf->remembertrash && (Mode == OMrunning) &&
2351           !NoHistoryUpdate && !InndHisRemember(HDR(HDR__MESSAGE_ID)))
2352           syslog(L_ERROR, "%s cant write history %s %m",
2353             LogName, HDR(HDR__MESSAGE_ID));
2354         ARTreject(REJECT_GROUP, cp, article);
2355         return false;
2356       } else {
2357         /* if !GroupMissing, then all the groups the article was posted
2358          * to have a flag of "x" in our active file, and therefore
2359          * we should throw the article away:  if you have set
2360          * innconf->remembertrash true, then you want all trash except that
2361          * which you explicitly excluded in your active file. */
2362         if (!GroupMissing) {
2363           if (innconf->remembertrash && (Mode == OMrunning) &&
2364               !NoHistoryUpdate && !InndHisRemember(HDR(HDR__MESSAGE_ID)))
2365             syslog(L_ERROR, "%s cant write history %s %m",
2366               LogName, HDR(HDR__MESSAGE_ID));
2367           ARTreject(REJECT_GROUP, cp, article);
2368             return false;
2369         }
2370       }
2371     }
2372     ngp = NGfind(ARTjnk);
2373     *ngptr++ = ngp;
2374     ngp->PostCount = 0;
2375
2376     /* Junk can be fed to other sites. */
2377     for (isp = ngp->Sites, i = ngp->nSites; --i >= 0; isp++) {
2378       if (*isp >= 0) {
2379         sp = &Sites[*isp];
2380         if (!sp->Poison && !(sp->ControlOnly && !IsControl))
2381           SITEmark(sp, ngp);
2382       }
2383     }
2384   }
2385   *ngptr = NULL;
2386
2387   if (innconf->xrefslave) {
2388     if (ARTxrefslave(data) == false) {
2389       if (HDR_FOUND(HDR__XREF)) {
2390         snprintf(cp->Error, sizeof(cp->Error),
2391                  "%d Xref header \"%s\" invalid in xrefslave mode",
2392                  NNTP_REJECTIT_VAL,
2393                  MaxLength(HDR(HDR__XREF), HDR(HDR__XREF)));
2394       } else {
2395         snprintf(cp->Error, sizeof(cp->Error),
2396                  "%d Xref header required in xrefslave mode",
2397                  NNTP_REJECTIT_VAL);
2398       }
2399       ARTlog(data, ART_REJECT, cp->Error);
2400       ARTreject(REJECT_OTHER, cp, article);
2401       return false;
2402     }
2403   } else {
2404     ARTassignnumbers(data);
2405   }
2406
2407   /* Now we can file it. */
2408   if (++ICDactivedirty >= innconf->icdsynccount) {
2409     ICDwriteactive();
2410     ICDactivedirty = 0;
2411   }
2412   TMRstart(TMR_ARTWRITE);
2413   for (i = 0; (ngp = GroupPointers[i]) != NULL; i++)
2414     ngp->PostCount = 0;
2415
2416   token = ARTstore(cp);
2417   /* change trailing '\r\n' to '\0\n' of all system header */
2418   for (i = 0 ; i < MAX_ARTHEADER ; i++) {
2419     if (HDR_FOUND(i))
2420       HDR_PARSE_START(i);
2421   }
2422   if (token.type == TOKEN_EMPTY) {
2423     syslog(L_ERROR, "%s cant store article: %s", LogName, SMerrorstr);
2424     snprintf(cp->Error, sizeof(cp->Error), "%d cant store article",
2425              NNTP_RESENDIT_VAL);
2426     ARTlog(data, ART_REJECT, cp->Error);
2427     if ((Mode == OMrunning) && !InndHisRemember(HDR(HDR__MESSAGE_ID)))
2428       syslog(L_ERROR, "%s cant write history %s %m", LogName,
2429         HDR(HDR__MESSAGE_ID));
2430     ARTreject(REJECT_OTHER, cp, article);
2431     TMRstop(TMR_ARTWRITE);
2432     return false;
2433   }
2434   TMRstop(TMR_ARTWRITE);
2435   if ((innconf->enableoverview && !innconf->useoverchan) || NeedOverview) {
2436     TMRstart(TMR_OVERV);
2437     ARTmakeoverview(cp);
2438     if (innconf->enableoverview && !innconf->useoverchan) {
2439       if ((result = OVadd(token, data->Overview.data, data->Overview.left,
2440         data->Arrived, data->Expires)) == OVADDFAILED) {
2441         if (OVctl(OVSPACE, (void *)&i) && i == OV_NOSPACE)
2442           IOError("creating overview", ENOSPC);
2443         else
2444           IOError("creating overview", 0);
2445         syslog(L_ERROR, "%s cant store overview for %s", LogName,
2446           TokenToText(token));
2447         OverviewCreated = false;
2448       } else {
2449         if (result == OVADDCOMPLETED)
2450           OverviewCreated = true;
2451         else
2452           OverviewCreated = false;
2453       }
2454     }
2455     TMRstop(TMR_OVERV);
2456   }
2457   strlcpy(data->TokenText, TokenToText(token), sizeof(data->TokenText));
2458
2459   /* Update history if we didn't get too many I/O errors above. */
2460   if ((Mode != OMrunning) ||
2461       !InndHisWrite(HDR(HDR__MESSAGE_ID), data->Arrived, data->Posted,
2462                     data->Expires, &token)) {
2463     i = errno;
2464     syslog(L_ERROR, "%s cant write history %s %m", LogName,
2465       HDR(HDR__MESSAGE_ID));
2466     snprintf(cp->Error, sizeof(cp->Error), "%d cant write history, %s",
2467              NNTP_RESENDIT_VAL, strerror(errno));
2468     ARTlog(data, ART_REJECT, cp->Error);
2469     ARTreject(REJECT_OTHER, cp, article);
2470     return false;
2471   }
2472
2473   if (NeedStoredGroup)
2474     data->StoredGroupLength = strlen(data->Newsgroups.List[0]);
2475
2476   /* Start logging, then propagate the article. */
2477   if (data->CRwithoutLF > 0 || data->LFwithoutCR > 0) {
2478     if (data->CRwithoutLF > 0 && data->LFwithoutCR == 0)
2479       snprintf(cp->Error, sizeof(cp->Error),
2480                "%d article includes CR without LF(%d)",
2481                NNTP_REJECTIT_VAL, data->CRwithoutLF);
2482     else if (data->CRwithoutLF == 0 && data->LFwithoutCR > 0)
2483       snprintf(cp->Error, sizeof(cp->Error),
2484                "%d article includes LF without CR(%d)",
2485                NNTP_REJECTIT_VAL, data->LFwithoutCR);
2486     else
2487       snprintf(cp->Error, sizeof(cp->Error),
2488                "%d article includes CR without LF(%d) and LF withtout CR(%d)",
2489                NNTP_REJECTIT_VAL, data->CRwithoutLF, data->LFwithoutCR);
2490     ARTlog(data, ART_STRSTR, cp->Error);
2491   }
2492   ARTlog(data, Accepted ? ART_ACCEPT : ART_JUNK, (char *)NULL);
2493   if ((innconf->nntplinklog) &&
2494     (fprintf(Log, " (%s)", data->TokenText) == EOF || ferror(Log))) {
2495     oerrno = errno;
2496     syslog(L_ERROR, "%s cant write log_nntplink %m", LogName);
2497     IOError("logging nntplink", oerrno);
2498     clearerr(Log);
2499   }
2500   /* Calculate Max Article Time */
2501   i = Now.time - cp->ArtBeg;
2502   if(i > cp->ArtMax)
2503     cp->ArtMax = i;
2504   cp->ArtBeg = 0;
2505
2506   cp->Size += data->BytesValue;
2507   if (innconf->logartsize) {
2508     if (fprintf(Log, " %ld", data->BytesValue) == EOF || ferror (Log)) {
2509       oerrno = errno;
2510       syslog(L_ERROR, "%s cant write artsize %m", LogName);
2511       IOError("logging artsize", oerrno);
2512       clearerr(Log);
2513     }
2514   }
2515
2516   ARTpropagate(data, (const char **)hops, hopcount, data->Distribution.List,
2517     ControlStore, OverviewCreated);
2518
2519   /* Now that it's been written, process the control message.  This has
2520    * a small window, if we get a new article before the newgroup message
2521    * has been processed.  We could pause ourselves here, but it doesn't
2522    * seem to be worth it. */
2523   if (Accepted) {
2524     if (IsControl) {
2525       ARTcontrol(data, HDR(HDR__CONTROL), cp);
2526     }
2527     if (DoCancels && HDR_FOUND(HDR__SUPERSEDES)) {
2528       if (ARTidok(HDR(HDR__SUPERSEDES)))
2529         ARTcancel(data, HDR(HDR__SUPERSEDES), false);
2530     }
2531   }
2532
2533   /* And finally, send to everyone who should get it */
2534   for (sp = Sites, i = nSites; --i >= 0; sp++) {
2535     if (sp->Sendit) {
2536       if (!Filtered || !sp->DropFiltered) {
2537         TMRstart(TMR_SITESEND);
2538         SITEsend(sp, data);
2539         TMRstop(TMR_SITESEND);
2540       }
2541     }
2542   }
2543
2544   return true;
2545 }