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