/* $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; }