4.4BSD/usr/src/contrib/news/inn/innd/art.c
/* $Revision: 1.52 $
**
** Article-processing.
*/
#include "innd.h"
#include "dbz.h"
#include <sys/uio.h>
typedef struct iovec IOVEC;
/*
** A way to index into the header table.
*/
#define HDR(_x) (ARTheaders[(_x)].Value)
#if defined(S_IXUSR)
#define EXECUTE_BITS (S_IXUSR | S_IXGRP | S_IXOTH)
#else
#define EXECUTE_BITS 0111
#endif /* defined(S_IXUSR) */
/*
** Mark that the site gets this article.
*/
#define SITEmark(sp_, ngp_) \
do { \
SITE *funnel; \
\
sp_->Sendit = TRUE; \
if (sp_->ng == NULL) \
sp_->ng = ngp_; \
if (sp_->Funnel != NOSITE) { \
funnel = &Sites[sp_->Funnel]; \
if (funnel->ng == NULL) \
funnel->ng = ngp_; \
} \
} while (JUSTONCE)
/*
** Header types.
*/
typedef enum _ARTHEADERTYPE {
HTreq, /* Drop article if this is missing */
HTobs, /* Delete this header if found */
HTstd /* Standard optional header */
} ARTHEADERTYPE;
/*
** Entry in the header table.
*/
typedef struct _ARTHEADER {
STRING Name;
ARTHEADERTYPE Type;
int Size; /* Length of Name */
char *Value;
int Length; /* Length of Value */
int Found;
BOOL Allocated;
} ARTHEADER;
/*
** For speed we build a binary tree of the headers, sorted by their
** name. We also store the header's Name fields in the tree to avoid
** doing an extra indirection.
*/
typedef struct _TREE {
STRING Name;
ARTHEADER *Header;
struct _TREE *Before;
struct _TREE *After;
} TREE;
STATIC TREE *ARTheadertree;
/*
** For doing the overview database, we keep a list of the headers and
** a flag saying if they're written in brief or full format.
*/
typedef struct _ARTOVERFIELD {
ARTHEADER *Header;
BOOL NeedHeader;
} ARTOVERFIELD;
STATIC ARTOVERFIELD *ARTfields;
/*
** General newsgroup we care about, and what we put in the Path line.
*/
STATIC char ARTctl[] = "control";
STATIC char ARTjnk[] = "junk";
STATIC char *ARTpathme;
/*
** Flag array, indexed by character. Character classes for Message-ID's.
*/
STATIC char ARTcclass[256];
#define CC_MSGID_ATOM 01
#define CC_MSGID_NORM 02
#define CC_HOSTNAME 04
#define ARTnormchar(c) ((ARTcclass[(c)] & CC_MSGID_NORM) != 0)
#define ARTatomchar(c) ((ARTcclass[(c)] & CC_MSGID_ATOM) != 0)
#define ARThostchar(c) ((ARTcclass[(c)] & CC_HOSTNAME) != 0)
/*
** The header table. Not necessarily sorted, but the first character
** must be uppercase.
*/
STATIC ARTHEADER ARTheaders[] = {
/* Name Type ... */
{ "Approved", HTstd },
#define _approved 0
{ "Control", HTstd },
#define _control 1
{ "Date", HTreq },
#define _date 2
{ "Distribution", HTstd },
#define _distribution 3
{ "Expires", HTstd },
#define _expires 4
{ "From", HTreq },
#define _from 5
{ "Lines", HTstd },
#define _lines 6
{ "Message-ID", HTreq },
#define _message_id 7
{ "Newsgroups", HTreq },
#define _newsgroups 8
{ "Path", HTreq },
#define _path 9
{ "Reply-To", HTstd },
#define _reply_to 10
{ "Sender", HTstd },
#define _sender 11
{ "Subject", HTreq },
#define _subject 12
{ "Supersedes", HTstd },
#define _supersedes 13
{ "Bytes", HTstd },
#define _bytes 14
{ "Also-Control", HTstd },
#define _alsocontrol 15
{ "References", HTstd },
#define _references 16
{ "Xref", HTobs },
#define _xref 17
{ "Date-Received", HTobs },
{ "Posted", HTobs },
{ "Posting-Version", HTobs },
{ "Received", HTobs },
{ "Relay-Version", HTobs },
};
/*
**
*/
BOOL
ARTreadschema()
{
static char SCHEMA[] = _PATH_SCHEMA;
register FILE *F;
register int i;
register char *p;
register ARTOVERFIELD *fp;
register ARTHEADER *hp;
BOOL ok;
char buff[SMBUF];
if (ARTfields != NULL) {
DISPOSE(ARTfields);
ARTfields = NULL;
}
/* Open file, count lines. */
if ((F = fopen(SCHEMA, "r")) == NULL)
return FALSE;
for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++)
continue;
(void)fseek(F, (OFFSET_T)0, SEEK_SET);
ARTfields = NEW(ARTOVERFIELD, i + 1);
/* Parse each field. */
for (ok = TRUE, fp = ARTfields; fgets(buff, sizeof buff, F) != NULL; ) {
/* Ignore blank and comment lines. */
if ((p = strchr(buff, '\n')) != NULL)
*p = '\0';
if ((p = strchr(buff, COMMENT_CHAR)) != NULL)
*p = '\0';
if (buff[0] == '\0')
continue;
if ((p = strchr(buff, ':')) != NULL) {
*p++ = '\0';
fp->NeedHeader = EQ(p, "full");
}
else
fp->NeedHeader = FALSE;
for (hp = ARTheaders; hp < ENDOF(ARTheaders); hp++)
if (EQ(buff, hp->Name)) {
fp->Header = hp;
break;
}
if (hp == ENDOF(ARTheaders)) {
syslog(L_ERROR, "%s bad_schema unknown header \"%s\"", buff);
ok = FALSE;
continue;
}
fp++;
}
fp->Header = NULL;
(void)fclose(F);
return ok;
}
/*
** Build a balanced tree for the headers in subscript range [lo..hi).
** This only gets called once, and the tree only has about 20 entries,
** so we don't bother to unroll the recursion.
*/
static TREE *
ARTbuildtree(Table, lo, hi)
ARTHEADER **Table;
int lo;
int hi;
{
int mid;
TREE *tp;
mid = lo + (hi - lo) / 2;
tp = NEW(TREE, 1);
tp->Header = Table[mid];
tp->Name = tp->Header->Name;
if (mid == lo)
tp->Before = NULL;
else
tp->Before = ARTbuildtree(Table, lo, mid);
if (mid == hi - 1)
tp->After = NULL;
else
tp->After = ARTbuildtree(Table, mid + 1, hi);
return tp;
}
/*
** Sorting predicate for qsort call in ARTsetup.
*/
STATIC int
ARTcompare(p1, p2)
POINTER p1;
POINTER p2;
{
ARTHEADER **h1;
ARTHEADER **h2;
h1 = CAST(ARTHEADER**, p1);
h2 = CAST(ARTHEADER**, p2);
return strcasecmp(h1[0]->Name, h2[0]->Name);
}
/*
** Setup the article processing.
*/
void
ARTsetup()
{
register STRING p;
register ARTHEADER *hp;
ARTHEADER **table;
register int i;
/* Set up the character class tables. These are written a
* little strangely to work around a GCC2.0 bug. */
(void)memset((POINTER)ARTcclass, 0, sizeof ARTcclass);
p = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
while ((i = *p++) != 0) {
ARTcclass[i] = CC_HOSTNAME | CC_MSGID_ATOM | CC_MSGID_NORM;
}
p = "!#$%&'*+-/=?^_`{|}~";
while ((i = *p++) != 0) {
ARTcclass[i] = CC_MSGID_ATOM | CC_MSGID_NORM;
}
p = "\"(),.:;<@[\\]";
while ((i = *p++) != 0) {
ARTcclass[i] = CC_MSGID_NORM;
}
/* The RFC's don't require it, but we add underscore to the list of valid
* hostname characters. */
ARTcclass['.'] |= CC_HOSTNAME;
ARTcclass['-'] |= CC_HOSTNAME;
ARTcclass['_'] |= CC_HOSTNAME;
/* Allocate space in the header table. */
for (hp = ARTheaders; hp < ENDOF(ARTheaders); hp++) {
hp->Size = strlen(hp->Name);
hp->Allocated = hp->Value == NULL && hp->Type != HTobs
&& hp != &ARTheaders[_bytes];
if (hp->Allocated)
hp->Value = NEW(char, MAXHEADERSIZE + 1);
}
/* Build the header tree. */
table = NEW(ARTHEADER*, SIZEOF(ARTheaders));
for (i = 0; i < SIZEOF(ARTheaders); i++)
table[i] = &ARTheaders[i];
qsort((POINTER)table, SIZEOF(ARTheaders), sizeof *table, ARTcompare);
ARTheadertree = ARTbuildtree(table, 0, SIZEOF(ARTheaders));
DISPOSE(table);
/* Get our Path name, kill trailing !. */
ARTpathme = COPY(Path.Data);
ARTpathme[Path.Used - 1] = '\0';
/* Set up database; ignore errors. */
(void)ARTreadschema();
}
STATIC void
ARTfreetree(tp)
TREE *tp;
{
TREE *next;
for ( ; tp != NULL; tp = next) {
if (tp->Before)
ARTfreetree(tp->Before);
next = tp->After;
DISPOSE(tp);
}
}
void
ARTclose()
{
register ARTHEADER *hp;
/* Free space in the header table. */
for (hp = ARTheaders; hp < ENDOF(ARTheaders); hp++)
if (hp->Allocated)
DISPOSE(hp->Value);
if (ARTfields != NULL) {
DISPOSE(ARTfields);
ARTfields = NULL;
}
ARTfreetree(ARTheadertree);
}
/*
** Read in a file, return a pointer to static space that is reused.
*/
STATIC char *
ARTreadfile(name)
char *name;
{
static BUFFER File;
struct stat Sb;
int fd;
int oerrno;
/* Open the file, get its size. */
if ((fd = open(name, O_RDONLY)) < 0)
return NULL;
if (fstat(fd, &Sb) < 0) {
oerrno = errno;
(void)close(fd);
errno = oerrno;
return NULL;
}
/* Make sure we have enough space. */
if (File.Size == 0) {
File.Size = Sb.st_size;
File.Data = NEW(char, File.Size + 1);
}
else if (File.Size <= Sb.st_size) {
File.Size = Sb.st_size + 16;
RENEW(File.Data, char, File.Size + 1);
}
/* Read in the file. */
if (xread(fd, File.Data, Sb.st_size) < 0) {
oerrno = errno;
(void)close(fd);
errno = oerrno;
return NULL;
}
/* Clean up and return the data. */
File.Data[Sb.st_size] = '\0';
(void)close(fd);
return File.Data;
}
/*
** Open the article file and return a copy of it. The files parameter is
** actually a whitespace-separated list of names.
*/
char *
ARTreadarticle(files)
register char *files;
{
register char *p;
register BOOL more;
char *art;
if (files == NULL)
return NULL;
/* Loop over all filenames until we can open one. */
for ( ; *files; files = p + 1) {
/* Snip off next name, turn dots to slashes. */
for (p = files; ISWHITE(*p); p++)
continue;
for (files = p; *p && *p != ' '; p++)
if (*p == '.')
*p = '/';
more = *p == ' ';
if (more)
*p = '\0';
art = ARTreadfile(files);
if (more)
*p = ' ';
if (art != NULL)
return art;
if (!more)
break;
}
return NULL;
}
/*
** Open the article file and return a copy of the headers.
*/
char *
ARTreadheader(files)
char *files;
{
register char *p;
register char *head;
if ((head = ARTreadarticle(files)) == NULL)
return NULL;
/* Find \n\n which means the end of the header. */
for (p = head; (p = strchr(p, '\n')) != NULL; p++)
if (p[1] == '\n') {
p[1] = '\0';
return head;
}
syslog(L_NOTICE, "%s bad_article %s is all headers", LogName, files);
DISPOSE(head);
return NULL;
}
/*
** Parse a Path line, splitting it up into NULL-terminated array of strings.
** The argument is modified!
*/
STATIC char **
ARTparsepath(p, countp)
register char *p;
int *countp;
{
static char *NULLPATH[1] = { NULL };
static int oldlength;
static char **hosts;
register int i;
register char **hp;
/* We can be called with a non-existant or empty path. */
if (p == NULL || *p == '\0') {
*countp = 0;
return NULLPATH;
}
/* Get an array of character pointers. */
i = strlen(p);
if (hosts == NULL) {
oldlength = i;
hosts = NEW(char*, oldlength + 1);
}
else if (oldlength <= i) {
oldlength = i;
RENEW(hosts, char*, oldlength + 1);
}
/* Loop over text. */
for (hp = hosts; *p; *p++ = '\0') {
/* Skip leading separators. */
for (; *p && !ARThostchar(*p); p++)
continue;
if (*p == '\0')
break;
/* Mark the start of the host, move to the end of it. */
for (*hp++ = p; *p && ARThostchar(*p); p++)
continue;
if (*p == '\0')
break;
}
*hp = NULL;
*countp = hp - hosts;
return hosts;
}
/*
** Write an article using writev. The article is split into pieces,
** shown below separated by pipe signs. The items in square brackets are
** "inserted" by this routine.
** |headers...
** Path: |[Path.Data]|rest of path...
** headers...
** |[Lines header, if needed]|
** |[Xref header]|
**
** Article body.
** Also, the Data->Size field is filled in.
*/
STATIC int
ARTwrite(name, Article, Data, CrossPosted)
char *name;
BUFFER *Article;
ARTDATA *Data;
BOOL CrossPosted;
{
static char WHEN[] = "article";
static char NL[] = "\n";
static BUFFER Headers;
register int fd;
register IOVEC *vp;
register long size;
register char *p;
IOVEC iov[7];
IOVEC *end;
char bytesbuff[SMBUF];
int i;
if ((p = HeaderFind(Article->Data, "Path", 4)) == NULL
|| p == Article->Data) {
/* This should not happen. */
syslog(L_ERROR, "%s internal %s no Path header",
Data->MessageID, LogName);
return -1;
}
/* Open the file. */
if ((fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, ARTFILE_MODE)) < 0) {
if (errno != ENOENT)
IOError(WHEN);
return -1;
}
/* Set up the scatter/gather vectors. */
vp = iov;
size = 0;
vp->iov_base = Article->Data;
vp->iov_len = p - Article->Data;
size += (vp++)->iov_len;
vp->iov_base = Path.Data;
vp->iov_len = Path.Used;
size += (vp++)->iov_len;
vp->iov_base = p;
vp->iov_len = Data->Body - p;
size += (vp++)->iov_len;
if (ARTheaders[_lines].Found == 0) {
(void)sprintf(Data->Lines, "Lines: %d\n", Data->LinesValue);
i = strlen(Data->Lines);
vp->iov_base = Data->Lines;
(vp++)->iov_len = i;
size += i;
/* Install in header table; STRLEN("Lines: ") == 7. */
(void)strcpy(ARTheaders[_lines].Value, Data->Lines + 7);
ARTheaders[_lines].Length = i - 7;
ARTheaders[_lines].Found = 1;
}
if (CrossPosted) {
/* Install in header table; STRLEN("Xref: ") == 6. */
HDR(_xref) = Xref.Data + 6;
ARTheaders[_xref].Length = Xref.Used - 6;
ARTheaders[_xref].Found = 1;
vp->iov_base = Xref.Data;
vp->iov_len = Xref.Used;
size += (vp++)->iov_len;
}
end = vp;
vp->iov_base = NL;
vp->iov_len = 1;
size += (vp++)->iov_len;
vp->iov_base = Data->Body;
vp->iov_len = &Article->Data[Article->Used] - Data->Body;
size += (vp++)->iov_len;
Data->SizeValue = size;
(void)sprintf(Data->Size, "%ld", Data->SizeValue);
Data->SizeLength = strlen(Data->Size);
HDR(_bytes) = Data->Size;
ARTheaders[_bytes].Length = Data->SizeLength;
ARTheaders[_bytes].Found = 1;
/* Now do the write. */
if (xwritev(fd, iov, vp - iov) < 0) {
IOError(WHEN);
syslog(L_ERROR, "%s cant write %s %m", LogName, name);
(void)close(fd);
if (unlink(name) < 0 && errno != ENOENT) {
IOError(WHEN);
syslog(L_ERROR, "%s cant unlink %s %m", LogName, name);
}
return -1;
}
if (close(fd) < 0) {
IOError(WHEN);
syslog(L_ERROR, "%s cant close %s %m", LogName, name);
if (unlink(name) < 0 && errno != ENOENT) {
IOError(WHEN);
syslog(L_ERROR, "%s cant unlink %s %m", LogName, name);
}
return -1;
}
/* Set the owner. */
if (AmRoot)
xchown(name);
/* Need the header data? */
if (!NeedHeaders)
return 0;
/* Figure out how much space we'll need and get it. */
(void)sprintf(bytesbuff, "Bytes: %ld\n", size);
for (i = strlen(bytesbuff), vp = iov; vp < end; vp++)
i += vp->iov_len;
if (!CrossPosted)
i += Xref.Used;
if (Headers.Data == NULL) {
Headers.Size = i;
Headers.Data = NEW(char, Headers.Size + 1);
}
else if (Headers.Size <= i) {
Headers.Size = i;
RENEW(Headers.Data, char, Headers.Size + 1);
}
/* Add the data. */
BUFFset(&Headers, bytesbuff, strlen(bytesbuff));
if (!CrossPosted)
BUFFappend(&Headers, Xref.Data, Xref.Used);
for (vp = iov; vp < end; vp++)
BUFFappend(&Headers, vp->iov_base, vp->iov_len);
Data->Headers = &Headers;
return 0;
}
/*
** Parse a header that starts at in, copying it to out. Return pointer to
** the start of the next header and fill in *deltap with what should
** get added to the output pointer. (This nicely lets us clobber obsolete
** headers by setting it to zero.)
*/
STATIC char *
ARTparseheader(in, out, deltap, errorp)
register char *in;
register char *out;
int *deltap;
STRING *errorp;
{
static char buff[SMBUF];
static char COLONSPACE[] = "No colon-space in \"%s\" header";
register char *start;
register TREE *tp;
register ARTHEADER *hp;
register char c;
register char *p;
register char *dest;
register int i;
register char *colon;
/* Find a non-continuation line. */
for (colon = NULL, start = out; ; ) {
switch (*in) {
case '\0':
*errorp = "EOF in headers";
return NULL;
case ':':
if (colon == NULL)
colon = out;
break;
}
if ((*out++ = *in++) == '\n' && !ISWHITE(*in))
break;
}
*deltap = out - start;
if (colon == NULL || !ISWHITE(colon[1])) {
if ((p = strchr(start, '\n')) != NULL)
*p = '\0';
(void)sprintf(buff, COLONSPACE, MaxLength(start, start));
*errorp = buff;
return NULL;
}
/* See if this is a system header. A fairly tightly-coded
* binary search. */
c = CTYPE(islower, *start) ? toupper(*start) : *start;
for (*colon = '\0', tp = ARTheadertree; tp; ) {
if ((i = c - tp->Name[0]) == 0
&& (i = strcasecmp(start, tp->Name)) == 0)
break;
if (i < 0)
tp = tp->Before;
else
tp = tp->After;
}
*colon = ':';
if (tp == NULL) {
/* Not a system header, make sure we have <word><colon><space>. */
for (p = colon; --p != start; )
if (ISWHITE(*p)) {
(void)sprintf(buff, "Space before colon in \"%s\" header",
MaxLength(start, start));
*errorp = buff;
return NULL;
}
return in;
}
/* Found a known header; is it obsolete? */
hp = tp->Header;
if (hp->Type == HTobs) {
*deltap = 0;
return in;
}
/* If body of header is all blanks, drop the header. */
for (p = colon + 1; ISWHITE(*p); p++)
continue;
if (*p == '\0' || *p == '\n') {
*deltap = 0;
return in;
}
hp->Found++;
/* Zap in the canonical form of the header, undoing the \0 that
* strcpy put out (strncpy() spec isn't trustable, unfortunately). */
(void)strcpy(start, hp->Name);
start[hp->Size] = ':';
/* Copy the header if not too big. */
i = (out - 1) - p;
if (i >= MAXHEADERSIZE) {
(void)sprintf(buff, "\"%s\" header too long", hp->Name);
*errorp = buff;
return NULL;
}
hp->Length = i;
if (i > MEMCPY_THRESHOLD) {
(void)memcpy((POINTER)hp->Value, (POINTER)p, (SIZE_T)i);
hp->Value[i] = '\0';
}
else {
for (dest = hp->Value, i++; --i > 0; )
*dest++ = *p++;
*dest = '\0';
}
return in;
}
/*
** Check Message-ID format based on RFC 822 grammar, except that (as per
** RFC 1036) whitespace, non-printing, and '>' characters are excluded.
** Based on code by Paul Eggert posted to news.software.b on 22-Nov-90
** in <#*tyo2'~n@twinsun.com>, with additional email discussion.
** Thanks, Paul.
*/
BOOL
ARTidok(save)
char *save;
{
register int c;
register char *p;
/* Scan local-part: "< atom|quoted [ . atom|quoted]" */
p = save;
if (*p++ != '<')
return FALSE;
for (; ; p++) {
if (ARTatomchar(*p))
while (ARTatomchar(*++p))
continue;
else {
if (*p++ != '"')
return FALSE;
for ( ; ; ) {
switch (c = *p++) {
case '\\':
c = *p++;
/* FALLTHROUGH */
default:
if (ARTnormchar(c))
continue;
return FALSE;
case '"':
break;
}
break;
}
}
if (*p != '.')
break;
}
/* Scan domain part: "@ atom|domain [ . atom|domain] > \0" */
if (*p++ != '@')
return FALSE;
for ( ; ; p++) {
if (ARTatomchar(*p))
while (ARTatomchar(*++p))
continue;
else {
if (*p++ != '[')
return FALSE;
for ( ; ; ) {
switch (c = *p++) {
case '\\':
c = *p++;
/* FALLTHROUGH */
default:
if (ARTnormchar(c))
continue;
/* FALLTHROUGH */
case '[':
return FALSE;
case ']':
break;
}
break;
}
}
if (*p != '.')
break;
}
return *p == '>' && *++p == '\0' && p - save <= DBZMAXKEY;
}
/*
** Clean up an article. This is mainly copying in-place, stripping bad
** headers. Also fill in the article data block with what we can find.
** Return NULL if the article is okay, or a string describing the error.
*/
STATIC STRING
ARTclean(Article, Data)
BUFFER *Article;
ARTDATA *Data;
{
static char buff[SMBUF];
ARTHEADER *hp;
register char *in;
register char *out;
register int i;
register char *p;
STRING error;
int delta;
/* Read through the headers one at a time. */
Data->Feedsite = "?";
Data->Size[0] = '0';
Data->Size[1] = '\0';
for (hp = ARTheaders; hp < ENDOF(ARTheaders); hp++) {
if (hp->Value && hp->Type != HTobs)
*hp->Value = '\0';
hp->Found = 0;
}
for (error = NULL, in = out = Article->Data; ; out += delta, in = p) {
if (*in == '\0') {
error = "No body";
break;
}
if (*in == '\n' && out > Article->Data && out[-1] == '\n')
/* Found a \n after another \n; break out. */
break;
/* Check the validity of this header. */
if ((p = ARTparseheader(in, out, &delta, &error)) == NULL)
break;
}
Data->Body = out;
in++;
/* Try to set this now, so we can report it in errors. */
p = HDR(_message_id);
if (*p) {
Data->MessageID = p;
Data->MessageIDLength = strlen(p);
if (error == NULL) {
if (Data->MessageIDLength > DBZMAXKEY)
error = "\"Message-ID\" header too long";
else if (!ARTidok(p))
error = "Bad \"Message-ID\" header";
}
}
if (error)
return error;
/* Make sure all the headers we need are there, and no duplicates. */
for (hp = ARTheaders; hp < ENDOF(ARTheaders); hp++)
if (hp->Type == HTreq) {
if (*hp->Value == '\0') {
(void)sprintf(buff, "Missing \"%s\" header", hp->Name);
return buff;
}
if (hp->Found > 1) {
(void)sprintf(buff, "Duplicate \"%s\" header", hp->Name);
return buff;
}
}
/* Scan the body, counting lines. */
for (i = 0; *in; ) {
if (*in == '\n')
i++;
*out++ = *in++;
}
*out = '\0';
Article->Used = out - Article->Data;
Data->LinesValue = i;
#if defined(DO_CHECK_LINECOUNT)
p = HDR(_lines);
if (*p && (delta = i - atoi(p)) != 0 && abs(delta) > LINECOUNT_FUZZ) {
if ((in = strchr(p, '\n')) != NULL)
*in = '\0';
(void)sprintf(buff, "Linecount %s != %d +- %d",
MaxLength(p, p), i, LINECOUNT_FUZZ);
return buff;
}
#endif /* defined(DO_CHECK_LINECOUNT) */
/* Is article too old? */
p = HDR(_date);
if ((Data->Posted = parsedate(p, &Now)) == -1) {
(void)sprintf(buff, "Bad \"Date\" header -- \"%s\"", MaxLength(p, p));
return buff;
}
if (Cutoff && Data->Posted < Now.time - Cutoff) {
(void)sprintf(buff, "Too old -- \"%s\"", MaxLength(p, p));
return buff;
}
if (Data->Posted > Now.time + DATE_FUZZ) {
(void)sprintf(buff, "Article posted in the future -- \"%s\"",
MaxLength(p, p));
return buff;
}
Data->Arrived = Now.time;
p = HDR(_expires);
Data->Expires = 0;
if (*p != '\0' && (Data->Expires = parsedate(p, &Now)) == -1) {
#if 0
(void)sprintf(buff, "Bad \"Expires\" header -- \"%s\"",
MaxLength(p, p));
return buff;
#endif
}
/* Whitespace in the Newsgroups header? */
for (p = HDR(_newsgroups); *p; p++)
if (ISWHITE(*p)) {
(void)sprintf(buff,
"Whitespace in \"Newsgroups\" header -- \"%s\"",
MaxLength(HDR(_newsgroups), p));
return buff;
}
/* If there is no control header, see if the article starts with
* "cmsg ". */
in = HDR(_control);
if (*in == '\0') {
p = HDR(_subject);
if (*p == 'c' && EQn(p, "cmsg ", 5)) {
for (p += 5; *p && ISWHITE(*p); )
p++;
if (*p)
(void)strcpy(in, p);
}
}
return NULL;
}
/*
** Start a log message about an article.
*/
STATIC void
ARTlog(Data, code, text)
ARTDATA *Data;
char code;
char *text;
{
int i;
BOOL Done;
/* We could be a bit faster by not dividing Now.usec by 1000,
* but who really wants to log at the Microsec level? */
Done = code == ART_ACCEPT || code == ART_JUNK;
if (text)
i = fprintf(Log, "%.15s.%03.3d %c %s %s %s%s",
ctime(&Now.time) + 4, (int)(Now.usec / 1000),
code, Data->Feedsite, Data->MessageID, text, Done ? "" : "\n");
else
i = fprintf(Log, "%.15s.%03.3d %c %s %s%s",
ctime(&Now.time) + 4, (int)(Now.usec / 1000),
code, Data->Feedsite, Data->MessageID, Done ? "" : "\n");
if (i == EOF || (Done && !BufferedLogs && fflush(Log)) || ferror(Log)) {
IOError("logging article");
syslog(L_ERROR, "%s cant write log_start %m", LogName);
clearerr(Log);
}
}
/*
** We are going to reject an article, record the reason and
** and the article. For now, this is just a placeholder.
*/
/* ARGSUSED0 */
STATIC void
ARTreject(buff, article)
char *buff;
BUFFER *article;
{
}
#if defined(DO_VERIFY_CANCELS)
/*
** Verify if a cancel message is valid. If the user posting the cancel
** matches the user who posted the article, return the list of filenames
** otherwise return NULL.
*/
STATIC char *
ARTcancelverify(Data, MessageID)
ARTDATA *Data;
char *MessageID;
{
register char *files;
register char *p;
register char *local;
char *head;
char buff[SMBUF];
files = HISfilesfor(MessageID);
if ((head = ARTreadheader(files)) == NULL)
return NULL;
/* Get the author header. */
if ((local = HeaderFind(head, "Sender", 6)) == NULL
&& (local = HeaderFind(head, "From", 4)) == NULL) {
syslog(L_ERROR, "%s bad_article %s checking cancel",
LogName, MessageID);
return NULL;
}
HeaderCleanFrom(local);
/* Compare canonical forms. */
p = COPY(Data->Poster);
HeaderCleanFrom(p);
if (!EQ(local, p)) {
files = NULL;
(void)sprintf(buff, "\"%.50s\" wants to cancel %s by \"%.50s\"",
p, MaxLength(MessageID, MessageID), local);
ARTlog(Data, ART_REJECT, buff);
}
DISPOSE(p);
return files;
}
#endif /* defined(DO_VERIFY_CANCELS) */
/*
** Process a cancel message.
*/
/* ARGSUSED2 */
void
ARTcancel(Data, MessageID, Trusted)
ARTDATA *Data;
char *MessageID;
BOOL Trusted;
{
register char *files;
register char *p;
register BOOL more;
STRING save;
char buff[SMBUF];
if (!HIShavearticle(MessageID)) {
/* Article hasn't arrived here, so write a fake entry using
* most of the information from the cancel message. */
#if defined(DO_VERIFY_CANCELS)
if (!Trusted)
return;
#endif /* defined(DO_VERIFY_CANCELS) */
save = Data->MessageID;
Data->MessageID = MessageID;
(void)HISwrite(Data, (char *)NULL);
Data->MessageID = save;
(void)sprintf(buff, "Cancelling %s", MessageID);
ARTlog(Data, ART_CANC, buff);
return;
}
#if defined(DO_VERIFY_CANCELS)
files = Trusted ? HISfilesfor(MessageID)
: ARTcancelverify(Data, MessageID);
#else
files = HISfilesfor(MessageID);
#endif /* !defined(DO_VERIFY_CANCELS) */
if (files == NULL)
return;
/* Get the files where the message is stored and and zap them. */
for ( ; *files; files = p + 1) {
/* Snip off next name, turn dots to slashes. */
for (p = files; ISWHITE(*p); p++)
continue;
for (files = p; *p && *p != ' '; p++)
if (*p == '.')
*p = '/';
more = *p == ' ';
if (more)
*p = '\0';
/* Remove this file, go back for the next one if there's more. */
if (unlink(files) < 0 && errno != ENOENT)
syslog(L_ERROR, "%s cant unlink %s %m", LogName, files);
if (!more)
break;
}
}
/*
** Process a control message. Cancels are handled here, but any others
** are passed out to an external program in a specific directory that
** has the same name as the first word of the control message.
*/
STATIC void
ARTcontrol(Data, Control)
ARTDATA *Data;
char *Control;
{
static char CTLBIN[] = _PATH_CONTROLPROGS;
register char *p;
char buff[SMBUF];
char *av[6];
struct stat Sb;
register char c;
/* See if it's a cancel message. */
c = *Control;
if (c == 'c' && EQn(Control, "cancel", 6)) {
for (p = &Control[6]; ISWHITE(*p); p++)
continue;
if (*p)
ARTcancel(Data, p, FALSE);
return;
}
/* Nip off the first word into lowercase. */
for (p = Control; *p && !ISWHITE(*p); p++)
if (CTYPE(isupper, *p))
*p = tolower(*p);
if (*p)
*p++ = '\0';
/* Treat the control message as a place to send the article, if
* the name is "safe" -- no slashes in the pathname. */
if (p - Control + STRLEN( _PATH_BADCONTROLPROG) >= SMBUF-4
|| strchr(Control, '/') != NULL)
FileGlue(buff, CTLBIN, '/', _PATH_BADCONTROLPROG);
else {
FileGlue(buff, CTLBIN, '/', Control);
if (stat(buff, &Sb) < 0 || (Sb.st_mode & EXECUTE_BITS) == 0)
FileGlue(buff, CTLBIN, '/', _PATH_BADCONTROLPROG);
}
/* If it's an ihave or sendme, check the site named in the message. */
if ((c == 'i' && EQ(Control, "ihave"))
|| (c == 's' && EQ(Control, "sendme"))) {
while (ISWHITE(*p))
p++;
if (*p == '\0') {
syslog(L_NOTICE, "%s malformed %s no site %s",
LogName, Control, Data->Name);
return;
}
if (EQ(p, ARTpathme)) {
/* Do nothing -- must have come from a replicant. */
syslog(L_NOTICE, "%s %s_from_me %s",
Data->Feedsite, Control, Data->Name);
return;
}
if (!SITEfind(p)) {
if (c == 'i')
syslog(L_ERROR, "%s bad_ihave in %s",
Data->Feedsite, Data->Newsgroups);
else
syslog(L_ERROR, "%s bad_sendme dont feed %s",
Data->Feedsite, Control, Data->Name);
return;
}
}
/* Build the command vector and execute it. */
av[0] = buff;
av[1] = COPY(Data->Poster);
av[2] = COPY(Data->Replyto);
av[3] = Data->Name;
av[4] = (char *)Data->Feedsite;
av[5] = NULL;
HeaderCleanFrom(av[1]);
HeaderCleanFrom(av[2]);
if (Spawn(STDIN, (int)fileno(Errlog), (int)fileno(Errlog), av) < 0)
/* We know the strrchr below can't fail. */
syslog(L_ERROR, "%s cant spawn %s for %s %m",
LogName, MaxLength(av[0], strrchr(av[0], '/')), Data->Name);
DISPOSE(av[1]);
DISPOSE(av[2]);
}
/*
** Split a Distribution header, making a copy and skipping leading and
** trailing whitespace (which the RFC allows).
*/
STATIC void
DISTparse(list, Data)
register char **list;
ARTDATA *Data;
{
static BUFFER Dist;
register char *p;
register char *q;
register int i;
register int j;
/* Get space to store the copy. */
for (i = 0, j = 0; (p = list[i]) != NULL; i++)
j += 1 + strlen(p);
if (Dist.Data == NULL) {
Dist.Size = j;
Dist.Data = NEW(char, Dist.Size + 1);
}
else if (Dist.Size <= j) {
Dist.Size = j + 16;
RENEW(Dist.Data, char, Dist.Size + 1);
}
/* Loop over each element, skip and trim whitespace. */
for (q = Dist.Data, i = 0, j = 0; (p = list[i]) != NULL; i++) {
while (ISWHITE(*p))
p++;
if (*p) {
if (j)
*q++ = ',';
for (list[j++] = p; *p && !ISWHITE(*p); )
*q++ = *p++;
*p = '\0';
}
}
list[j] = NULL;
*q = '\0';
Data->Distribution = Dist.Data;
Data->DistributionLength = q - Dist.Data;
}
/*
** A somewhat similar routine, except that this handles negated entries
** in the list and is used to check the distribution sub-field.
*/
STATIC BOOL
DISTwanted(list, p)
register char **list;
register char *p;
{
register char *q;
register char c;
register BOOL sawbang;
for (sawbang = FALSE, c = *p; (q = *list) != NULL; list++)
if (*q == '!') {
sawbang = TRUE;
if (c == *++q && EQ(p, q))
return FALSE;
}
else if (c == *q && EQ(p, q))
return TRUE;
/* If we saw any !foo's and didn't match, then assume they are all
* negated distributions and return TRUE, else return false. */
return sawbang;
}
/*
** See if any of the distributions in the article are wanted by the site.
*/
STATIC BOOL
DISTwantany(site, article)
char **site;
register char **article;
{
for ( ; *article; article++)
if (DISTwanted(site, *article))
return TRUE;
return FALSE;
}
/*
** Sort an array of newsgroups for optimal disk access. This may be
** of marginal benefit.
*/
STATIC void
ARTsortfordisk()
{
static NEWSGROUP *save;
register NEWSGROUP **ngptr;
if (save && GroupPointers[1] != NULL) {
/* If one of the groups we want to access is the group we last
* wrote to, move it to the front of the list. */
for (ngptr = GroupPointers; *++ngptr; )
if (*ngptr == save) {
*ngptr = GroupPointers[0];
GroupPointers[0] = save;
return;
}
}
save = GroupPointers[0];
}
/*
** Send the current article to all sites that would get it if the
** group were created.
*/
STATIC void
ARTsendthegroup(name)
register char *name;
{
register SITE *sp;
register int i;
NEWSGROUP *ngp;
for (ngp = NGfind(ARTctl), sp = Sites, i = nSites; --i >= 0; sp++)
if (sp->Name != NULL && SITEwantsgroup(sp, name)) {
SITEmark(sp, ngp);
}
}
/*
** Assign article numbers to the article and create the Xref line.
** If we end up not being able to write the article, we'll get "holes"
** in the directory and active file.
*/
STATIC void
ARTassignnumbers()
{
register char *p;
register int i;
register NEWSGROUP *ngp;
p = &Xref.Data[Xref.Used];
for (i = 0; (ngp = GroupPointers[i]) != NULL; i++) {
/* If already went to this group (i.e., multiple groups are aliased
* into it), then skip it. */
if (ngp->PostCount > 0)
continue;
/* Bump the number. */
ngp->PostCount++;
ngp->Last++;
if (!FormatLong(ngp->LastString, (long)ngp->Last, ngp->Lastwidth)) {
syslog(L_ERROR, "%s cant update_active %s", LogName, ngp->Name);
continue;
}
ngp->Filenum = ngp->Last;
(void)sprintf(p, " %s:%lu", ngp->Name, ngp->Filenum);
p += strlen(p);
}
Xref.Used = p - Xref.Data;
Xref.Data[Xref.Used++] = '\n';
}
/*
** Parse the data from the xreplic command and assign the numbers.
** This involves replacing the GroupPointers entries.
*/
STATIC void
ARTreplic(Replic, CrossPostedp)
BUFFER *Replic;
BOOL *CrossPostedp;
{
register char *p;
register char *q;
register char *name;
register char *next;
register NEWSGROUP *ngp;
register int i;
p = &Xref.Data[Xref.Used];
for (i = 0, name = Replic->Data; *name; name = next) {
/* Mark end of this entry and where next one starts. */
if ((next = strchr(name, ',')) != NULL)
*next++ = '\0';
else
next = "";
/* Split into news.group/# */
if ((q = strchr(name, '/')) == NULL) {
syslog(L_ERROR, "%s bad_format %s", LogName, name);
continue;
}
*q = '\0';
if ((ngp = NGfind(name)) == NULL) {
syslog(L_ERROR, "%s bad_newsgroup %s", LogName, name);
continue;
}
ngp->Filenum = atol(q + 1);
/* Update active file if we got a new high-water mark. */
if (ngp->Last < ngp->Filenum) {
ngp->Last = ngp->Filenum;
if (!FormatLong(ngp->LastString, (long)ngp->Last,
ngp->Lastwidth)) {
syslog(L_ERROR, "%s cant update_active %s",
LogName, ngp->Name);
continue;
}
}
/* Mark that this group gets the article. */
ngp->PostCount++;
GroupPointers[i++] = ngp;
/* Turn news.group/# into news.group:#, append to Xref. */
*q = ':';
*p++ = ' ';
p += strlen(strcpy(p, name));
}
*CrossPostedp = i > 1 || AlwaysCrosspost;
Xref.Used = p - Xref.Data;
Xref.Data[Xref.Used++] = '\n';
}
/*
** Return TRUE if a list of strings has a specific one. This is a
** generic routine, but is used for seeing if a host is in the Path line.
*/
STATIC BOOL
ListHas(list, p)
register char **list;
register char *p;
{
register char *q;
register char c;
for (c = *p; (q = *list) != NULL; list++)
if (c == *q && caseEQ(p, q))
return TRUE;
return FALSE;
}
/*
** Propagate an article to the sites have "expressed an interest."
*/
STATIC void
ARTpropagate(Data, hops, hopcount, list)
ARTDATA *Data;
char **hops;
int hopcount;
char **list;
{
register SITE *sp;
register int i;
register int j;
register int Groupcount;
register char *p;
register SITE *funnel;
register BUFFER *bp;
/* Work out which sites should really get it. */
Groupcount = Data->Groupcount;
for (sp = Sites, i = nSites; --i >= 0; sp++) {
if (sp->Seenit || !sp->Sendit)
continue;
sp->Sendit = FALSE;
if (sp->Master != NOSITE && Sites[sp->Master].Seenit)
continue;
if (sp->MaxSize && sp->MaxSize < Data->SizeValue)
/* Too big for the site. */
continue;
if ((!sp->IgnorePath && ListHas(hops, sp->Name))
|| (sp->Hops && hopcount > sp->Hops)
|| (sp->Groupcount && Groupcount > sp->Groupcount))
/* Site already saw the article; path too long; or too much
* cross-posting. */
continue;
if (list
&& sp->Distributions
&& !DISTwantany(sp->Distributions, list))
/* Not in the site's desired list of distributions. */
continue;
if (sp->DistRequired && list == NULL)
/* Site requires Distribution header and there isn't one. */
continue;
if (sp->Exclusions) {
for (j = 0; (p = sp->Exclusions[j]) != NULL; j++)
if (ListHas(hops, p))
break;
if (p != NULL)
/* A host in the site's exclusion list was in the Path. */
continue;
}
/* Write that the site is getting it, and flag to send it. */
if (fprintf(Log, " %s", sp->Name) == EOF || ferror(Log)) {
IOError("logging site");
syslog(L_ERROR, "%s cant write log_site %m", LogName);
clearerr(Log);
}
sp->Sendit = TRUE;
sp->Seenit = TRUE;
if (sp->Master != NOSITE)
Sites[sp->Master].Seenit = TRUE;
}
if (putc('\n', Log) == EOF
|| (!BufferedLogs && fflush(Log))
|| ferror(Log)) {
syslog(L_ERROR, "%s cant write log_end %m", LogName);
clearerr(Log);
}
/* Handle funnel sites. */
for (sp = Sites, i = nSites; --i >= 0; sp++)
if (sp->Sendit && sp->Funnel != NOSITE) {
sp->Sendit = FALSE;
funnel = &Sites[sp->Funnel];
funnel->Sendit = TRUE;
if (funnel->FNLwantsnames) {
bp = &funnel->FNLnames;
p = &bp->Data[bp->Used];
if (bp->Used) {
*p++ = ' ';
bp->Used++;
}
bp->Used += strlen(strcpy(p, sp->Name));
}
}
}
/*
** Build up the overview data.
*/
STATIC void
ARTmakeoverview(Data)
ARTDATA *Data;
{
static char SEP[] = "\t";
static char COLONSPACE[] = ": ";
static BUFFER Overview;
register ARTOVERFIELD *fp;
register ARTHEADER *hp;
register char *p;
register int i;
/* Setup. */
if (Overview.Data == NULL)
Overview.Data = NEW(char, 1);
Data->Overview = &Overview;
BUFFset(&Overview, Xref.Data + Xrefbase + 1, Xref.Used - (Xrefbase + 2));
for (i = Overview.Left, p = Overview.Data; --i >= 0; p++)
if (*p == '.' || *p == ':')
*p = '/';
if (ARTfields == NULL) {
/* User error. */
return;
}
/* Write the data, a field at a time. */
for (fp = ARTfields; fp->Header; fp++) {
BUFFappend(&Overview, SEP, STRLEN(SEP));
hp = fp->Header;
if (!hp->Found)
continue;
if (fp->NeedHeader) {
BUFFappend(&Overview, hp->Name, hp->Size);
BUFFappend(&Overview, COLONSPACE, STRLEN(COLONSPACE));
}
i = Overview.Left;
BUFFappend(&Overview, hp->Value, hp->Length);
for (p = &Overview.Data[i]; i < Overview.Left; p++, i++)
if (*p == '\t' || *p == '\n')
*p = ' ';
}
}
/*
** This routine is the heart of it all. Take a full article, parse it,
** file or reject it, feed it to the other sites. Return the NNTP
** message to send back.
*/
STRING
ARTpost(cp, Replic, ihave)
CHANNEL *cp;
BUFFER *Replic;
char *ihave;
{
static char errNOSPACE[] = NNTP_RESENDIT_NOSPACE;
static char errNOHIST[] = NNTP_RESENDIT_NOHIST;
static BUFFER Files;
static BUFFER Header;
static char buff[SPOOLNAMEBUFF];
register char *p;
register int i;
register int j;
register NEWSGROUP *ngp;
register NEWSGROUP **ngptr;
register int *isp;
register SITE *sp;
ARTDATA Data;
BOOL Approved;
BOOL Accepted;
BOOL LikeNewgroup;
BOOL CrossPosted;
BOOL ToGroup;
BOOL GroupMissing;
BUFFER *article;
char linkname[SPOOLNAMEBUFF];
char **groups;
char **hops;
int hopcount;
char **distributions;
STRING error;
char ControlWord[SMBUF];
int ControlHeader;
/* Preliminary clean-ups. */
article = &cp->In;
Data.MessageID = ihave;
error = ARTclean(article, &Data);
/* Fill in other Data fields. */
Data.Poster = HDR(_sender);
if (*Data.Poster == '\0')
Data.Poster = HDR(_from);
Data.Replyto = HDR(_reply_to);
if (*Data.Replyto == '\0')
Data.Replyto = HDR(_from);
hops = ARTparsepath(HDR(_path), &hopcount);
#if defined(DO_IPADDR_LOG)
Data.Feedsite = RChostname(cp);
if (Data.Feedsite == NULL)
Data.Feedsite = CHANname(cp);
#else
Data.Feedsite = hops && hops[0] ? hops[0] : CHANname(cp);
#endif /* defined(DO_IPADDRLOG) */
Data.FeedsiteLength = strlen(Data.Feedsite);
(void)sprintf(Data.TimeReceived, "%lu", Now.time);
Data.TimeReceivedLength = strlen(Data.TimeReceived);
/* A duplicate? */
if (error == NULL && HIShavearticle(Data.MessageID))
error = "Duplicate article";
/* Now see if we got an error in the article. */
if (error != NULL) {
(void)sprintf(buff, "%d %s", NNTP_REJECTIT_VAL, error);
ARTlog(&Data, ART_REJECT, buff);
ARTreject(buff, article);
return buff;
}
/* Stash a copy of the Newsgroups header. */
p = HDR(_newsgroups);
i = strlen(p);
if (Header.Data == NULL) {
Header.Size = i;
Header.Data = NEW(char, Header.Size + 1);
}
else if (Header.Size <= i) {
Header.Size = i + 16;
RENEW(Header.Data, char, Header.Size + 1);
}
(void)strcpy(Header.Data, p);
Data.Newsgroups = Header.Data;
Data.NewsgroupsLength = i;
/* If we limit what distributions we get, see if we want this one. */
p = HDR(_distribution);
distributions = *p ? CommaSplit(p) : NULL;
if (distributions) {
DISTparse(distributions, &Data);
if (ME.Distributions
&& !DISTwantany(ME.Distributions, distributions)) {
(void)sprintf(buff, "%d Unwanted distribution \"%s\"",
NNTP_REJECTIT_VAL,
MaxLength(distributions[0], distributions[0]));
ARTlog(&Data, ART_REJECT, buff);
DISPOSE(distributions);
ARTreject(buff, article);
return buff;
}
}
else {
Data.Distribution = "?";
Data.DistributionLength = 1;
}
/* Clear all groups and sites -- assume nobody gets the article. */
for (i = nGroups, ngp = Groups; --i >= 0; ngp++)
ngp->PostCount = 0;
for (i = nSites, sp = Sites; --i >= 0; sp++) {
sp->Sendit = FALSE;
sp->Seenit = FALSE;
sp->FNLnames.Used = 0;
sp->ng = NULL;
}
/* Parse the Control or Also-Control header. */
groups = NGsplit(HDR(_newsgroups));
for (i = 0; groups[i] != NULL; i++)
continue;
Data.Groupcount = i;
if (HDR(_control)[0] != '\0')
ControlHeader = _control;
else if (HDR(_alsocontrol)[0] != '\0')
ControlHeader = _alsocontrol;
else {
ControlHeader = -1;
LikeNewgroup = FALSE;
}
if (ControlHeader >= 0) {
/* Nip off the first word into lowercase. */
(void)strncpy(ControlWord, HDR(ControlHeader), sizeof ControlWord);
ControlWord[sizeof ControlWord - 1] = '\0';
for (p = ControlWord; *p && !ISWHITE(*p); p++)
if (CTYPE(isupper, *p))
*p = tolower(*p);
*p = '\0';
LikeNewgroup = EQ(ControlWord, "newgroup")
|| EQ(ControlWord, "rmgroup");
/* Control messages to "foo.ctl" are treated as if they were
* posted to "foo". I should probably apologize for all the
* side-effects in the if. */
for (i = 0; (p = groups[i++]) != NULL; )
if ((j = strlen(p) - 4) > 0
&& *(p += j) == '.'
&& p[1] == 'c' && p[2] == 't' && p[3] == 'l')
*p = '\0';
}
/* Loop over the newsgroups, see which ones we want, and get the
* total space needed for the Xref line. At the end of this section
* of code, j will have the needed length, the appropriate site
* entries will have their Sendit and ng fields set, and GroupPointers
* will have pointers to the relevant newsgroups. */
ToGroup = FALSE;
p = HDR(_approved);
Approved = *p != '\0';
ngptr = GroupPointers;
j = 0;
for (GroupMissing = Accepted = FALSE; (p = *groups) != NULL; groups++) {
if (!RCcanpost(cp, p))
continue;
if ((ngp = NGfind(p)) == NULL) {
GroupMissing = TRUE;
if (LikeNewgroup && Approved) {
/* Newgroup/rmgroup being sent to a group that doesn't
* exist. Assume it is being sent to the group being
* created or removed, nd send the group to all sites that
* would or would have had the group if it were created. */
ARTsendthegroup(*groups);
Accepted = TRUE;
}
#if defined(DO_MERGE_TO_GROUPS)
/* Try to collapse all "to" newsgroups. */
if (*p != 't' || *++p != 'o' || *++p != '.' || *++p == '\0')
continue;
ngp = NGfind("to");
ToGroup = TRUE;
if ((sp = SITEfind(p)) != NULL) {
SITEmark(sp, ngp);
}
#else
continue;
#endif /* defined(DO_MERGE_TO_GROUPS) */
}
/* Ignore this group? */
if (ngp->Rest[0] == NF_FLAG_IGNORE)
continue;
/* Basic validity check. */
if (ngp->Rest[0] == NF_FLAG_MODERATED && !Approved) {
(void)sprintf(buff, "%d Unapproved for \"%s\"",
NNTP_REJECTIT_VAL, ngp->Name);
ARTlog(&Data, ART_REJECT, buff);
if (distributions)
DISPOSE(distributions);
ARTreject(buff, article);
return buff;
}
/* Valid group, feed it to that group's sites. */
Accepted = TRUE;
for (isp = ngp->Sites, i = ngp->nSites; --i >= 0; isp++)
if (*isp >= 0) {
sp = &Sites[*isp];
SITEmark(sp, ngp);
}
/* If it's excluded, don't file it. */
if (ngp->Rest[0] == NF_FLAG_EXCLUDED)
continue;
/* Expand aliases, mark the article as getting filed in the group. */
if (ngp->Alias != NULL)
ngp = ngp->Alias;
*ngptr++ = ngp;
ngp->PostCount = 0;
j += ngp->NameLength + 1 + MAXARTFNAME + 1;
}
/* Control messages not filed in "to" get filed only in controlname
* or control. */
if (ControlHeader >= 0 && Accepted && !ToGroup) {
FileGlue(buff, "control", '.', ControlWord);
if ((ngp = NGfind(buff)) == NULL)
ngp = NGfind(ARTctl);
ngp->PostCount = 0;
ngptr = GroupPointers;
*ngptr++ = ngp;
j = ngp->NameLength + 1 + MAXARTFNAME;
for (isp = ngp->Sites, i = ngp->nSites; --i >= 0; isp++)
if (*isp >= 0) {
sp = &Sites[*isp];
SITEmark(sp, ngp);
}
}
/* If !Accepted, then none of the article's newgroups exist in our
* active file. Proper action is to drop the article on the floor.
* If ngp == GroupPointers, then all the new articles newsgroups are
* "j" entries in the active file. In that case, we have to file it
* under junk so that downstream feeds can get it. */
if (!Accepted || ngptr == GroupPointers) {
if (!Accepted) {
(void)sprintf(buff, "%d Unwanted newsgroup \"%s\"",
NNTP_REJECTIT_VAL,
MaxLength(HDR(_newsgroups), HDR(_newsgroups)));
ARTlog(&Data, ART_REJECT, buff);
#if defined(DONT_WANT_TRASH)
#if defined(DO_REMEMBER_TRASH)
if (Mode == OMrunning && !HISwrite(&Data, ""))
syslog(L_ERROR, "%s cant write history %s %m",
LogName, Data.MessageID);
#endif /* defined(DO_REMEMBER_TRASH) */
if (distributions)
DISPOSE(distributions);
ARTreject(buff, article);
return buff;
#else
/* if !GroupMissing, then all the groups the article was posted
* to have a flag of "x" in our active file, and therefore
* we should throw the article away: if you have define
* DO_WANT_TRASH, then you want all trash except that which
* you explicitly excluded in your active file. */
if (!GroupMissing) {
if (distributions)
DISPOSE(distributions);
ARTreject(buff, article);
return buff;
}
#endif /* defined(DONT_WANT_TRASH) */
}
ngp = NGfind(ARTjnk);
*ngptr++ = ngp;
ngp->PostCount = 0;
j = STRLEN(ARTjnk) + 1 + MAXARTFNAME;
/* Junk can be fed to other sites. */
for (isp = ngp->Sites, i = ngp->nSites; --i >= 0; isp++)
if (*isp >= 0) {
sp = &Sites[*isp];
SITEmark(sp, ngp);
}
}
*ngptr = NULL;
CrossPosted = ngptr > &GroupPointers[1] || AlwaysCrosspost;
j++;
if (Replic)
j = Replic->Used + 1;
/* Make sure the Xref buffer has room. */
Xref.Used = Xrefbase;
if (Xref.Size <= j + Xrefbase + 2) {
Xref.Size = j + Xrefbase + 2;
RENEW(Xref.Data, char, Xref.Size + 1);
}
/* Make sure the filename buffer has room. */
if (Files.Data == NULL) {
Files.Size = j;
Files.Data = NEW(char, Files.Size + 1);
}
else if (Files.Size <= j) {
Files.Size = j;
RENEW(Files.Data, char, Files.Size + 1);
}
/* Assign article numbers, fill in Xref buffer. */
if (Replic == NULL)
ARTassignnumbers();
else
ARTreplic(Replic, &CrossPosted);
/* Optimize how we place the article on the disk. */
ARTsortfordisk();
/* Now we can file it. */
if (++ICDactivedirty >= ICD_SYNC_COUNT) {
ICDwriteactive();
ICDactivedirty = 0;
}
Data.Name[0] = '\0';
p = Files.Data;
*p = '\0';
for (i = 0; (ngp = GroupPointers[i]) != NULL; i++) {
if (!ngp->PostCount)
continue;
ngp->PostCount = 0;
if (Data.Name[0] == '\0') {
/* Write the article the first time. */
(void)sprintf(Data.Name, "%s/%lu", ngp->Dir, ngp->Filenum);
if (ARTwrite(Data.Name, article, &Data, CrossPosted) < 0
&& (!MakeSpoolDirectory(ngp->Dir)
|| ARTwrite(Data.Name, article, &Data, CrossPosted) < 0)) {
syslog(L_ERROR, "%s cant write %s %m", LogName, Data.Name);
ARTlog(&Data, ART_REJECT, errNOSPACE);
if (distributions)
DISPOSE(distributions);
ARTreject(buff, article);
return errNOSPACE;
}
p += strlen(strcpy(p, Data.Name));
Data.NameLength = strlen(Data.Name);
}
else {
/* Link to the main article. */
(void)sprintf(linkname, "%s/%lu", ngp->Dir, ngp->Filenum);
if (link(Data.Name, linkname) < 0
&& (!MakeSpoolDirectory(ngp->Dir)
|| link(Data.Name, linkname) < 0)) {
#if defined(DONT_HAVE_SYMLINK)
IOError("linking article");
syslog(L_ERROR, "%s cant link %s and %s %m",
LogName, Data.Name, linkname);
continue;
#else
/* Try to make a symbolic link to the full pathname. */
FileGlue(buff, SPOOL, '/', Data.Name);
if (symlink(buff, linkname) < 0
&& (!MakeSpoolDirectory(ngp->Dir)
|| symlink(buff, linkname) < 0)) {
IOError("symlinking article");
syslog(L_ERROR, "%s cant symlink %s and %s %m",
LogName, buff, linkname);
continue;
}
#endif /* defined(DONT_HAVE_SYMLINK) */
}
*p++ = ' ';
p += strlen(strcpy(p, linkname));
}
}
/* Update history if we didn't get too many I/O errors above. */
if (Mode != OMrunning || !HISwrite(&Data, Files.Data)) {
syslog(L_ERROR, "%s cant write history %s %m", LogName, Data.MessageID);
ARTlog(&Data, ART_REJECT, errNOHIST);
if (distributions)
DISPOSE(distributions);
ARTreject(buff, article);
return errNOHIST;
}
/* If we just flushed the active (above), now flush history. */
if (ICDactivedirty == 0)
HISsync();
/* We wrote the history, so modify it and save it for output. */
for (Data.Replic = Files.Data, p = (char *)Data.Replic; *p; p++)
if (*p == ' ')
*p = ',';
Data.ReplicLength = p - Data.Replic;
/* Start logging, then propagate the article. */
ARTlog(&Data, Accepted ? ART_ACCEPT : ART_JUNK, (char *)NULL);
#if defined(DO_NNTPLINK_LOG)
if (fprintf(Log, " (%s)", Data.Name) == EOF || ferror(Log)) {
IOError("logging nntplink");
syslog(L_ERROR, "%s cant write log_nntplink %m", LogName);
clearerr(Log);
}
#endif /* defined(DO_NNTPLINK_LOG) */
ARTpropagate(&Data, hops, hopcount, distributions);
if (distributions)
DISPOSE(distributions);
/* Now that it's been written, process the control message. This has
* a small window, if we get a new article before the newgroup message
* has been processed. We could pause ourselves here, but it doesn't
* seem to be worth it. */
if (Accepted) {
if (ControlHeader >= 0)
ARTcontrol(&Data, HDR(ControlHeader));
p = HDR(_supersedes);
if (*p)
ARTcancel(&Data, p, FALSE);
}
/* If we need the overview data, write it. */
if (NeedOverview)
ARTmakeoverview(&Data);
/* And finally, send to everyone who should get it */
for (sp = Sites, i = nSites; --i >= 0; sp++)
if (sp->Sendit)
SITEsend(sp, &Data);
return NNTP_TOOKIT;
}