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