/* TODO: backoff algorithm */
/* TODO: moderator mail error check */
/* TODO: check if authenticated users are properly put in X-Trace */
/*
* POST.C
*
* Post an article. The article is posted to one or several of the
* upstream servers depending on prioritization and the existence of
* the 'P'ost flag.
*
* The article is loaded into memory prior to posting. If no servers
* are available, the article is written to a local posting queue and
* the post is retried later.
*
* (c)Copyright 1998, Matthew Dillon, All Rights Reserved. Refer to
* the COPYRIGHT file in the base directory of this distribution
* for specific rights granted.
*/
#include "defs.h"
#ifdef POST_FORCEMSGID
#ifndef POST_CKPATHLOOP
#error "Please go back and read the message in vendor.h about POST_FORCEMSGID"
#endif
#endif
#ifdef POST_CFILTERHOOK
char *PostCFilter(char *base, int artLen, Connection *conn);
#endif
Prototype void NNPostBuffer(Connection *conn);
Prototype void NNPostCommand1(Connection *conn);
void NNPostResponse1(Connection *conn);
void NNPostArticle1(Connection *conn);
void NNPostResponse2(Connection *conn);
int MailIfModerated(char *groups, Connection *conn);
int groupAlready(int v, int *save, int saveCount);
void MailToModerator(const char *group, Connection *conn, const char *modMail);
int CheckGroupModeration(const char *group);
void GenerateDistribution(MBufHead *mbuf, char *newsGrps);
int BreakIfBadGroup(char *groups, Connection *conn);
int CheckValidRestrictedGroup(const char *group);
#ifdef POST_HIDENNTPPOSTHOST
/* JG19990908 don't publicize NNPH's, but still present a NNPH field that
can be used to identify posts from the same node (i.e. for things like
UPS and DSRS). Hash changes every 1hour. */
char *NNPHMangle(char *vserv, char *node, char *cryptpw)
{
char buffer[256], md5buf[33];
static char res[256];
/* This string is arbitrary, as long as it is the same for
any given node, you're ok */
snprintf(buffer, sizeof(buffer), "%s/%d/%s/%s", node, (int)time(NULL) / 3600, vserv, cryptpw);
/* Safety: in the event we cant md5, then leave some useful
data in md5buf */
snprintf(md5buf, sizeof(md5buf), "%s", node);
#ifdef __APPLE_CC__
MD5(buffer, strlen(buffer), md5buf);
#else
MD5Data(buffer, strlen(buffer), md5buf);
#endif
/* Clip the string. 8 digits is enough */
md5buf[8] = '\0';
snprintf(res, sizeof(res), "%s.%s", md5buf, vserv);
return(res);
}
#endif
#ifdef POST_CRYPTXTRACE
/* JG19990908 encrypt (for loose def'n of encrypt, heh) X-Trace, presenting
a uuencode-style line that can be decoded by abuse */
char *XTRAMangle(char *xtrace, char *cryptpw)
{
static char res[256];
char *rptr, *xptr;
int count;
unsigned char plain[8], enc[8];
des_cblock key;
des_key_schedule sched;
des_string_to_key(cryptpw, &key);
des_set_key(&key, sched);
rptr = res;
/* Overflow protect; 192 chars expands to 256, overflowing res */
if (strlen(xtrace) > 191) {
xtrace[191] = '\0';
}
/* Step thru 'xtrace' 8 chars at a time, encrypt as we go */
xptr = xtrace;
count = 0;
bzero(plain, sizeof(plain));
while (*xptr) {
plain[count++ % 8] = *xptr++;
if (! *xptr || (count % 8) == 0) {
/* Encrypt block of (up to) 8 and pump out coded ASCII */
des_ecb_encrypt((des_cblock *)plain,(des_cblock *)enc, sched, 1);
bzero(plain, sizeof(plain));
*rptr++ = '0' + (enc[0] & 0x3f);
*rptr++ = '0' + (enc[0] >> 6) + ((enc[1] & 0x0f) << 2);
*rptr++ = '0' + (enc[1] >> 4) + ((enc[2] & 0x03) << 4);
*rptr++ = '0' + (enc[2] >> 2);
*rptr++ = '0' + (enc[3] & 0x3f);
*rptr++ = '0' + (enc[3] >> 6) + ((enc[4] & 0x0f) << 2);
*rptr++ = '0' + (enc[4] >> 4) + ((enc[5] & 0x03) << 4);
*rptr++ = '0' + (enc[5] >> 2);
*rptr++ = '0' + (enc[6] & 0x3f);
*rptr++ = '0' + (enc[6] >> 6) + ((enc[7] & 0x0f) << 2);
*rptr++ = '0' + (enc[7] >> 4) + ((time(NULL) & 0x03) << 4);
}
}
*rptr = '\0';
return(res);
}
char *XTRAUnmangle(char *str, char *cryptpw)
{
static char res[256];
char *rptr, *sptr;
int count, i;
unsigned char plain[8], enc[8];
des_cblock key;
des_key_schedule sched;
des_string_to_key(cryptpw, &key);
des_set_key(&key, sched);
if (! strncmp(str, "DXC=", 4)) {
str += 4;
}
*res = '\0';
if (strlen(str) % 11) {
snprintf(res, sizeof(res), "(X-Trace damaged; may not decode OK) ");
}
rptr = res + strlen(res);
sptr = str;
count = 0;
bzero(enc, sizeof(enc));
while (*sptr) {
switch (count % 11) {
case 0:
enc[0] = (*sptr - '0');
break;
case 1:
enc[0] += ((*sptr - '0') & 0x03) << 6;
enc[1] = (*sptr - '0') >> 2;
break;
case 2:
enc[1] += ((*sptr - '0') & 0x0f) << 4;
enc[2] = (*sptr - '0') >> 4;
break;
case 3:
enc[2] += (*sptr - '0') << 2;
break;
case 4:
enc[3] = (*sptr - '0');
break;
case 5:
enc[3] += ((*sptr - '0') & 0x03) << 6;
enc[4] = (*sptr - '0') >> 2;
break;
case 6:
enc[4] += ((*sptr - '0') & 0x0f) << 4;
enc[5] = (*sptr - '0') >> 4;
break;
case 7:
enc[5] += (*sptr - '0') << 2;
break;
case 8:
enc[6] = (*sptr - '0');
break;
case 9:
enc[6] += ((*sptr - '0') & 0x03) << 6;
enc[7] = (*sptr - '0') >> 2;
break;
case 10:
enc[7] += ((*sptr - '0') & 0x0f) << 4;
break;
}
count++;
sptr++;
if (! *sptr || (count % 11) == 0) {
/* Decrypt block of (up to) 8 and pump out plain ASCII */
des_ecb_encrypt((des_cblock *)enc,(des_cblock *)plain, sched, 0);
bzero(enc, sizeof(enc));
for (i = 0; i < 8; i++) {
*rptr++ = plain[i];
}
}
}
*rptr = '\0';
return(res);
}
#endif
/*
* NNPostBuffer() - called when client connection post buffer contains the
* entire article. We need to check the article headers
* and post it as appropriate. If we are unable to post it
* immediately, we queue it.
*
* STEPS:
* (1) remove headers otherwise generated internally (for security)
* (2) add internally generated headers
*
* Internally generated headers:
* Path: (removed, generated)
* Date: (removed, generated)
* Lines: (removed, generated)
* Message-ID: (checked, generated)
* NNTP-Posting-Date: (removed, generated - optionally)
* NNTP-Posting-Host: (removed, generated)
* X-Trace: (removed, generated)
* Xref: (removed, RIP)
* Distribution: (checked, generated)
*
* Errors:
* 441 Article has no body -- just headers
* 441 Required "XXX" header is missing (Subject, From, Newsgroups)
* 441 435 Bad Message-ID
*/
void
NNPostBuffer(Connection *conn)
{
int artLen;
int haveMsgId = 0;
int haveSubject = 0;
int haveFrom = 0;
int haveDate = 0;
int haveApproved = 0;
int haveNewsgroups = 0;
int haveOrganization = 0;
int haveDist = 0;
const char *errMsg = NULL;
char *newsGrps = NULL;
char *base = MBNormalize(&conn->co_ArtBuf, &artLen);
#ifdef POST_CRYPTXTRACE
char xtrace[256];
#endif
MBufHead tmbuf;
MBInit(&tmbuf, -1, &conn->co_MemPool, &conn->co_BufPool);
if (!errMsg && conn->co_Flags & COF_POSTTOOBIG) {
conn->co_Flags &= ~COF_POSTTOOBIG;
errMsg = "441 Article too big for this server\r\n";
}
#ifdef POST_CFILTERHOOK
if (! errMsg) {
errMsg = PostCFilter(base, artLen, conn);
}
#endif
/*
* Output Path: line
*/
MBPrintf(&tmbuf, "Path: %s%snot-for-mail\r\n",
conn->co_Auth.dr_VServerDef->vs_PostPath,
(*conn->co_Auth.dr_VServerDef->vs_PostPath ? "!" : ""));
/*
* Scan Headers, check for required headers
*/
while (artLen > 1 && !(base[0] == '\n') && !(base[0] == '\r' && base[1] == '\n')) {
int l;
int keep = 1;
/*
* extract header. Headers can be multi-line so deal with that.
*/
for (l = 0; l < artLen; ++l) {
if (base[l] == '\r' &&
base[l+1] == '\n' &&
(l + 2 >= artLen || (base[l+2] != ' ' && base[l+2] != '\t'))
) {
l += 2;
break;
}
}
/*
* check against specials
*/
if (strncasecmp(base, "Subject:", 8) == 0) {
haveSubject = 1;
} else if (strncasecmp(base, "From:", 5) == 0) {
#ifdef POST_CKADDRESS
char *ptr = zallocStrTrim(&conn->co_MemPool, base+5, l - 5);
if (ptr && *ptr) {
HeaderCleanFrom(ptr);
/*
* If somebody realm authenticated, then they are
* unconditionally allowed to use that address to post
*/
if (! (strchr(ptr, '@') && *conn->co_Auth.dr_AuthUser && ! strcasecmp(ptr, conn->co_Auth.dr_AuthUser))) {
/*
* Otherwise we sanity check the address
*/
if (ckaddress(ptr)) {
errMsg = "441 From: address not in Internet syntax\r\n";
}
}
}
zfreeStr(&conn->co_MemPool, &ptr);
#endif
haveFrom = 1;
} else if (strncasecmp(base, "Approved:", 9) == 0) {
haveApproved = 1;
} else if (strncasecmp(base, "Newsgroups:", 11) == 0) {
haveNewsgroups = 1;
newsGrps = zallocStrTrim(&conn->co_MemPool, base + 11, l - 11);
} else if (strncasecmp(base, "Organization:", 13) == 0) {
haveOrganization = 1;
} else if (strncasecmp(base, "Date:", 5) == 0) {
haveDate = 1;
} else if (strncasecmp(base, "Distribution:", 13) == 0) {
/*
* Only keep the header and set haveDist if it has something in
* it, otherwise we (potentially) override it.
*/
{
char *ptr = zallocStrTrim(&conn->co_MemPool, base+13, l - 13);
if (ptr && *ptr)
haveDist = 1;
zfreeStr(&conn->co_MemPool, &ptr);
}
if (haveDist == 0)
keep = 0;
} else if (strncasecmp(base, "Path:", 5) == 0) {
/* JG19990907 path element count and reject */
char *ptr = zallocStrTrim(&conn->co_MemPool, base+5, l - 5);
if (conn->co_Auth.dr_ReaderDef->rd_PathComponents && ptr && *ptr) {
char *p = ptr;
int count = 0;
while ((p = strchr(p + 1, '!')))
count++;
if (count > conn->co_Auth.dr_ReaderDef->rd_PathComponents)
errMsg = "441 Can't inject non-local news article\r\n";
#ifdef POST_CKPATHLOOP
if (strstr(ptr, POST_CKPATHLOOP))
errMsg = "441 Can't reinject news article\r\n";
#endif
}
zfreeStr(&conn->co_MemPool, &ptr);
keep = 0; /* delete */
} else if (strncasecmp(base, "Date:", 5) == 0) {
keep = 0; /* delete */
} else if (strncasecmp(base, "Lines:", 6) == 0) {
keep = 0; /* delete */
} else if (strncasecmp(base, "Message-ID:", 11) == 0) {
#ifdef POST_FORCEMSGID
keep = 0; /* delete */
#else
int save = base[l-2];
const char *msgmsgid;
base[l-2] = 0;
if (strcmp((msgmsgid = MsgId(base + 11, NULL)), "<>") == 0 ||
strchr(base, '@') == NULL
) {
keep = 0; /* delete */
errMsg = "441 435 Bad Message-ID\r\n";
if (conn->co_IHaveMsgId)
zfreeStr(&conn->co_MemPool, &conn->co_IHaveMsgId);
} else {
keep = 1;
haveMsgId = 1;
conn->co_IHaveMsgId = zallocStr(&conn->co_MemPool, msgmsgid);
}
base[l-2] = save;
#endif
#ifdef POST_BOFHCLEANUP
/* JG19990304 other cleanups */
} else if (strncasecmp(base, "Date-Received:", 14) == 0) {
errMsg = "441 Obsolete \"Date-Received\" header rejected\r\n";
} else if (strncasecmp(base, "Received:", 9) == 0) {
errMsg = "441 Obsolete \"Received\" header rejected\r\n";
} else if (strncasecmp(base, "Posted:", 7) == 0) {
errMsg = "441 Obsolete \"Posted\" header rejected\r\n";
} else if (strncasecmp(base, "Posting-Version:", 16) == 0) {
errMsg = "441 Obsolete \"Posting-Version\" header rejected\r\n";
} else if (strncasecmp(base, "Relay-Version:", 14) == 0) {
errMsg = "441 Obsolete \"Relay-Version\" header rejected\r\n";
} else if (strncasecmp(base, "To:", 3) == 0) {
errMsg = "441 Obsolete \"To\" header rejected\r\n";
} else if (strncasecmp(base, "Cc:", 3) == 0) {
errMsg = "441 Obsolete \"Cc\" header rejected\r\n";
#endif
} else if (strncasecmp(base, "X-Complaints-To:", 16) == 0) {
errMsg = "441 Illegal \"X-Complaints-To\" header rejected\r\n";
} else if (strncasecmp(base, "NNTP-Posting-Date:", 18) == 0) {
errMsg = "441 Illegal \"NNTP-Posting-Date\" header rejected\r\n";
} else if (strncasecmp(base, "NNTP-Posting-Host:", 18) == 0) {
errMsg = "441 Illegal \"NNTP-Posting-Host\" header rejected\r\n";
} else if (strncasecmp(base, "X-Trace:", 8) == 0) {
errMsg = "441 Illegal \"X-Trace\" header rejected\r\n";
} else if (strncasecmp(base, "Xref:", 5) == 0) {
keep = 0; /* delete */
} else if (strncasecmp(base, "Control:", 8) == 0) {
if (strncasecmp(base, "Control: cancel", 15)) {
if (! (conn->co_Auth.dr_Flags & DF_CONTROLPOST)) {
errMsg = "441 Illegal \"Control\" message rejected\r\n";
}
} else {
#ifdef POST_RESTRICTCANCEL
char *ptr = zallocStrTrim(&conn->co_MemPool, base+15, l - 15);
if (ptr && *ptr && (strstr(ptr,
conn->co_Auth.dr_VServerDef->vs_ClusterName) == 0))
errMsg = "441 Illegal Cancel Message-ID\r\n";
zfreeStr(&conn->co_MemPool, &ptr);
#endif
}
} else if (strncasecmp(base, "Also-Control:", 13) == 0) {
if (strncasecmp(base, "Also-Control: cancel", 20)) {
if (! (conn->co_Auth.dr_Flags & DF_CONTROLPOST))
errMsg = "441 Illegal \"Control\" message rejected\r\n";
} else {
#ifdef POST_RESTRICTCANCEL
char *ptr = zallocStrTrim(&conn->co_MemPool, base+20, l - 20);
if (ptr && *ptr && (strstr(ptr,
conn->co_Auth.dr_VServerDef->vs_ClusterName) == 0))
errMsg = "441 Illegal Cancel Message-ID\r\n";
zfreeStr(&conn->co_MemPool, &ptr);
#endif
}
} else if (strncasecmp(base, "Supersedes:", 11) == 0) {
#ifdef POST_RESTRICTCANCEL
char *ptr = zallocStrTrim(&conn->co_MemPool, base+11, l - 11);
if (ptr && *ptr && (strstr(ptr,
conn->co_Auth.dr_VServerDef->vs_ClusterName) == 0))
errMsg = "441 Illegal Supersedes Message-ID\r\n";
zfreeStr(&conn->co_MemPool, &ptr);
#endif
}
if (keep)
MBWrite(&tmbuf, base, l);
base += l;
artLen -= l;
}
/*
* Check errors
*/
if (haveNewsgroups == 0) {
errMsg = "441 Required \"Newsgroups\" header is missing\r\n";
} else if (conn->co_Auth.dr_ReaderDef->rd_CheckPostGroups &&
BreakIfBadGroup(newsGrps, conn) != 0) {
errMsg = "441 Nonexistent newsgroup(s)\r\n";
}
if (haveFrom == 0)
errMsg = "441 Required \"From\" header is missing\r\n";
if (haveSubject == 0)
errMsg = "441 Required \"Subject\" header is missing\r\n";
if (artLen <= 2)
errMsg = "441 Article has no body -- just headers\r\n";
/*
* If no error, add our own headers
*/
if (errMsg == NULL) {
time_t t = time(NULL);
/*
* Add Date:
*/
if (haveDate == 0) {
struct tm *tp = gmtime(&t);
char buf[64];
strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S GMT", tp);
MBPrintf(&tmbuf, "Date: %s\r\n", buf);
}
/*
* Add Lines:
*/
{
int lines = 0;
int i;
for (i = 2; i < artLen; ++i) {
if (base[i] == '\n')
++lines;
}
if (base[i-1] != '\n') /* last line not terminated? */
++lines;
MBPrintf(&tmbuf, "Lines: %d\r\n", lines);
}
/*
* Add Message-ID:
*/
if (haveMsgId == 0) {
if (conn->co_IHaveMsgId == NULL)
GenerateMessageID(conn);
MBPrintf(&tmbuf, "Message-ID: %s\r\n", conn->co_IHaveMsgId);
}
/*
* Add Distribution:
*/
if (haveDist == 0 && newsGrps) {
GenerateDistribution(&tmbuf, newsGrps);
}
if (!haveOrganization && *conn->co_Auth.dr_VServerDef->vs_Org) {
MBPrintf(&tmbuf, "Organization: %s\r\n",
conn->co_Auth.dr_VServerDef->vs_Org);
}
if (*conn->co_Auth.dr_VServerDef->vs_Comments) {
if (! strstr(conn->co_Auth.dr_VServerDef->vs_Comments, "\\n")) {
MBPrintf(&tmbuf, "X-Comments: %s\r\n",
conn->co_Auth.dr_VServerDef->vs_Comments);
} else {
/* This is sinful even for me. JG20030118 */
char buf[512], *ptr, *head;
int commentnum = 1;
snprintf(buf, sizeof(buf), "%s", conn->co_Auth.dr_VServerDef->vs_Comments);
head = buf;
while ((ptr = strstr(head, "\\n"))) {
*ptr = '\0';
MBPrintf(&tmbuf, "X-Comments-%d: %s\r\n",
commentnum++, head);
head = ptr + 2;
}
MBPrintf(&tmbuf, "X-Comments-%d: %s\r\n",
commentnum++, head);
}
}
#if defined POST_NNTP_POSTING_DATE_GMT || defined POST_NNTP_POSTING_DATE_LOCALTIME
/*
* Add NNTP-Posting-Date:
*/
{
char buf[64];
struct tm *tp;
#ifdef POST_NNTP_POSTING_DATE_GMT
tp = gmtime(&t);
strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S GMT", tp);
#else
tp = localtime(&t);
strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S %Z", tp);
#endif
MBPrintf(&tmbuf, "NNTP-Posting-Date: %s\r\n", buf);
}
#endif
/*
* Add NNTP-Posting-Host:
*/
#ifdef POST_HIDENNTPPOSTHOST
if (*conn->co_Auth.dr_VServerDef->vs_CryptPw)
MBPrintf(&tmbuf, "NNTP-Posting-Host: %s\r\n",
NNPHMangle(conn->co_Auth.dr_VServerDef->vs_ClusterName,
conn->co_Auth.dr_Host,
conn->co_Auth.dr_VServerDef->vs_CryptPw));
else
#endif
MBPrintf(&tmbuf, "NNTP-Posting-Host: %s\r\n", conn->co_Auth.dr_Host);
/*
* Add X-Trace:
*/
#ifdef POST_CRYPTXTRACE
if (*conn->co_Auth.dr_VServerDef->vs_CryptPw) {
snprintf(xtrace, sizeof(xtrace), "%lu %s %d %s%s%s%s%s",
(unsigned long)t,
conn->co_Auth.dr_VServerDef->vs_HostName,
(int)getpid(), /* should probably be slot id */
*conn->co_Auth.dr_AuthUser ? conn->co_Auth.dr_AuthUser : "",
*conn->co_Auth.dr_AuthUser ? "/" : "",
*conn->co_Auth.dr_IdentUser ? conn->co_Auth.dr_IdentUser : "",
*conn->co_Auth.dr_IdentUser ? "@" : "",
NetAddrToSt(0, (struct sockaddr *)&conn->co_Auth.dr_Addr, 1, 1, 1)
);
MBPrintf(&tmbuf, "X-Trace: DXC=%s\r\n",
XTRAMangle(xtrace, conn->co_Auth.dr_VServerDef->vs_CryptPw));
} else
#endif
MBPrintf(&tmbuf, "X-Trace: %lu %s %d %s%s%s%s%s\r\n",
(unsigned long)t,
conn->co_Auth.dr_VServerDef->vs_HostName,
(int)getpid(), /* should probably be slot id */
*conn->co_Auth.dr_AuthUser ? conn->co_Auth.dr_AuthUser : "",
*conn->co_Auth.dr_AuthUser ? "/" : "",
*conn->co_Auth.dr_IdentUser ? conn->co_Auth.dr_IdentUser : "",
*conn->co_Auth.dr_IdentUser ? "@" : "",
NetAddrToSt(0, (struct sockaddr *)&conn->co_Auth.dr_Addr, 1, 1, 1)
);
if (*conn->co_Auth.dr_VServerDef->vs_AbuseTo)
MBPrintf(&tmbuf, "X-Complaints-To: %s\r\n",
conn->co_Auth.dr_VServerDef->vs_AbuseTo);
}
/*
* blank line and article body
*/
if (errMsg == NULL) {
MBWrite(&tmbuf, base, artLen);
}
if (*conn->co_Auth.dr_VServerDef->vs_PostTrailer) {
/* This is sinful even for me. JG20030206 */
char buf[512], *ptr, *head;
snprintf(buf, sizeof(buf), "%s", conn->co_Auth.dr_VServerDef->vs_PostTrailer);
head = buf;
while ((ptr = strstr(head, "\\n"))) {
*ptr = '\0';
MBPrintf(&tmbuf, "%s\r\n", head);
head = ptr + 2;
}
MBPrintf(&tmbuf, "%s\r\n", head);
}
MBFree(&conn->co_ArtBuf);
/*
* If no error, pump out to ready server or queue for transmission,
* else write the error out and throw away.
*
* When POSTing to a moderated group, the article is mailed to the
* moderator and NOT otherwise posted, even if crossposted to non-moderated
* groups.
*/
if (errMsg == NULL) {
/*
* Copy into co_ArtBuf for transmission/mailing
*/
MBCopy(&tmbuf, &conn->co_ArtBuf);
MBFree(&tmbuf);
logit(
LOG_NOTICE,
"%s%s%s%s%s (%s) post ok %s %d",
*conn->co_Auth.dr_AuthUser ? conn->co_Auth.dr_AuthUser : "",
*conn->co_Auth.dr_AuthUser ? "/" : "",
(conn->co_Auth.dr_IdentUser[0] ? conn->co_Auth.dr_IdentUser : ""),
(conn->co_Auth.dr_IdentUser[0] ? "@" : ""),
conn->co_Auth.dr_Host,
NetAddrToSt(0, (struct sockaddr *)&conn->co_Auth.dr_Addr, 1, 1, 1),
conn->co_IHaveMsgId,
conn->co_ArtBuf.mh_Bytes
);
conn->co_ClientPostCount++;
conn->co_Auth.dr_PostCount++;
conn->co_Auth.dr_PostBytes += conn->co_ArtBuf.mh_Bytes;
if (newsGrps &&
haveApproved == 0 &&
MailIfModerated(newsGrps, conn) == 0
) {
if (conn && conn->co_IHaveMsgId)
zfreeStr(&conn->co_MemPool, &conn->co_IHaveMsgId);
MBFree(&conn->co_ArtBuf);
MBLogPrintf(conn, &conn->co_TMBuf, "240 Article mailed to moderator\r\n");
NNCommand(conn);
} else {
NNServerRequest(conn, NULL, conn->co_IHaveMsgId, SREQ_POST, 0, -1, 0);
}
} else {
MBLogPrintf(conn, &conn->co_TMBuf, "%s", errMsg);
MBFree(&tmbuf);
conn->co_Auth.dr_PostFailCount++;
NNCommand(conn);
}
if (newsGrps)
zfreeStr(&conn->co_MemPool, &newsGrps);
}
/*
* NNPostCommand1() - called when article has been queued to a server
* connection, in the server's context, to post the
* article.
*
* SEQUENCE:
*/
void
NNPostCommand1(Connection *conn)
{
ServReq *sreq = conn->co_SReq;
MBLogPrintf(conn, &conn->co_TMBuf, "ihave %s\r\n", sreq->sr_MsgId);
NNPostResponse1(conn);
}
void
NNPostResponse1(Connection *conn)
{
/*ServReq *sreq = conn->co_SReq;*/
char *buf;
int len;
conn->co_Func = NNPostResponse1;
conn->co_State = "pores1";
if ((len = MBReadLine(&conn->co_RMBuf, &buf)) > 0) {
LogCmd(conn, '<', buf);
if (strtol(buf, NULL, 10) == 335) {
NNPostArticle1(conn);
} else {
char errMsg[256];
MBFree(&conn->co_ArtBuf);
if (len >= 1 && buf[len-1] == 0)
--len;
if (len >= 1 && buf[len-1] == '\r')
--len;
if (len > sizeof(errMsg) - 32)
len = sizeof(errMsg) - 32;
strcpy(errMsg, "441 ");
bcopy(buf, errMsg + 4, len);
errMsg[4+len] = '\r';
errMsg[5+len] = '\n';
errMsg[6+len] = 0;
NNFinishSReq(conn, errMsg, 0);
}
} else if (len < 0) {
NNServerTerminate(conn);
} /* else we haven't gotten the reponse yet */
}
void
NNPostArticle1(Connection *conn)
{
ServReq *sreq = conn->co_SReq;
conn->co_Func = NNPostArticle1;
conn->co_State = "poart1";
/* Verify that the client has not disconnected yet. If such is the
* case, don't bother returning an error message, there's no client
*/
if (sreq == NULL || sreq->sr_CConn == NULL) {
logit(LOG_DEBUG, "Client vanished during post (%s)", sreq->sr_MsgId);
MBPrintf(&conn->co_TMBuf, ".\r\n"); /* Just junk the post. */
NNPostResponse2(conn);
return;
}
while (conn->co_TMBuf.mh_Bytes < 800) {
char *buf;
int len;
if ((len = MBReadLine(&sreq->sr_CConn->co_ArtBuf, &buf)) > 0) {
buf[len-1] = '\n';
MBWrite(&conn->co_TMBuf, buf, len);
} else if (len <= 0) {
MBPrintf(&conn->co_TMBuf, ".\r\n");
NNPostResponse2(conn);
return;
}
}
}
void
NNPostResponse2(Connection *conn)
{
char *buf;
int len;
conn->co_Func = NNPostResponse2;
conn->co_State = "pores2";
if ((len = MBReadLine(&conn->co_RMBuf, &buf)) > 0) {
LogCmd(conn, '<', buf);
if (conn->co_SReq == NULL || conn->co_SReq->sr_CConn == NULL) {
/* Client has gone away. we don't care any more */
NNFinishSReq(conn, NULL, 0);
} else if (strtol(buf, NULL, 10) == 235) {
char msgbuf[1024];
snprintf(msgbuf, sizeof(msgbuf), "240 %s Article posted\r\n",
conn->co_SReq->sr_CConn->co_IHaveMsgId != NULL ?
conn->co_SReq->sr_CConn->co_IHaveMsgId : "");
NNFinishSReq(conn, msgbuf, 0);
} else {
char errMsg[256];
MBFree(&conn->co_ArtBuf);
if (len >= 1 && buf[len-1] == 0)
--len;
if (len >= 1 && buf[len-1] == '\r')
--len;
if (len > sizeof(errMsg) - 32)
len = sizeof(errMsg) - 32;
strcpy(errMsg, "441 ");
bcopy(buf, errMsg + 4, len);
errMsg[4+len] = '\r';
errMsg[5+len] = '\n';
errMsg[6+len] = 0;
NNFinishSReq(conn, errMsg, 0);
}
if (conn->co_SReq != NULL && conn->co_SReq->sr_CConn != NULL &&
conn->co_SReq->sr_CConn->co_IHaveMsgId != NULL)
zfreeStr(&conn->co_SReq->sr_CConn->co_MemPool,
&conn->co_SReq->sr_CConn->co_IHaveMsgId);
} else if (len < 0) {
NNServerTerminate(conn);
} /* else we haven't gotten the reponse yet */
}
/*
* BreakIfBadGroup() - if posting to a nonexistent or S=x group, stop
*
*/
int
BreakIfBadGroup(char *groups, Connection *conn)
{
int r = 0;
int rval = 0;
struct GroupList *accessgroups = conn->co_Auth.dr_PostGroupDef->gr_Groups;
int i = 0;
while (groups[i]) {
int j;
char c;
for (j = i; groups[j] && groups[j] != ','; ++j)
;
if (j - i > MAXGNAME) {
r = -1;
break;
}
c = groups[j];
groups[j] = 0;
if (i != j) {
if (accessgroups != NULL && !GroupFindWild(groups + i, accessgroups)) {
r = -1;
break;
}
if ((rval = CheckValidRestrictedGroup(groups + i))) {
r = rval;
}
}
i = j;
if ((groups[j] = c) == ',')
++i;
}
return(r);
}
/*
* MailIfModerated() - if posting to moderated groups and the article
* was not approved, mail the message to the moderators
* instead
*
* Return 0 if article was mailed, -1 otherwise
*
*/
int
MailIfModerated(char *groups, Connection *conn)
{
FILE *fi;
int r = -1;
int saveCount = 0;
char buf[256];
int save[256];
if ((fi = fopen(PatLibExpand(ModeratorsPat), "r")) != NULL) {
while (fgets(buf, sizeof(buf), fi) != NULL) {
char *modGroupWild = strtok(buf, ": \t\r\n");
char *modMail = (modGroupWild) ? strtok(NULL, ": \t\r\n") : NULL;
int i = 0;
if (modGroupWild == NULL || modMail == NULL)
continue;
while (groups[i]) {
int j;
char c;
for (j = i; groups[j] && groups[j] != ','; ++j)
;
c = groups[j];
groups[j] = 0;
if (i != j &&
groupAlready(i, save, saveCount) < 0 &&
saveCount != arysize(save)
) {
if (CheckGroupModeration(groups + i) < 0)
save[saveCount++] = i;
}
if (i != j &&
groupAlready(i, save, saveCount) < 0 &&
saveCount != arysize(save) &&
WildCmp(modGroupWild, groups + i
) == 0) {
r = 0;
MailToModerator(groups + i, conn, modMail);
save[saveCount++] = i;
}
i = j;
if ((groups[j] = c) == ',')
++i;
}
}
fclose(fi);
}
return(r);
}
int
groupAlready(int v, int *save, int saveCount)
{
int i;
int r = -1;
for (i = 0; i < saveCount; ++i) {
if (save[i] == v) {
r = 0;
break;
}
}
return(r);
}
/*
* MailToModerator() - send mail to the moderator.
*
* Note: we have to unescape the posted article and we cannot pass
* certain headers. We cannot pass Path: because the moderator may
* forward it, causing the posting news machine to miss the approved
* posting made by the moderator (because it will have locked itself
* out due to the Path: ). We also do not pass To:, Cc:, or Bcc:
* headers for two reasons: First, because the news client is
* supposed to handle those fields and we don't parse them for
* non-moderated postings, and second, for security reasons.
*/
void
MailToModerator(const char *group, Connection *conn, const char *modMail)
{
char mailDest[256];
pid_t pid;
int fds[3];
char *argv[5] = { SENDMAIL_PATH, SENDMAIL_ARG0, "-t", "-i", NULL };
{
char *mgroup = zallocStr(&conn->co_MemPool, group);
int i;
for (i = 0; mgroup[i]; ++i) {
if (mgroup[i] == '.') /* dots to dashes */
mgroup[i] = '-';
if (mgroup[i] == '@' || mgroup[i] == ',') /* shouldn't happen */
mgroup[i] = '-';
}
snprintf(mailDest, sizeof(mailDest), modMail, mgroup);
zfreeStr(&conn->co_MemPool, &mgroup);
}
if ((pid = RunProgramPipe(fds, RPF_STDOUT, argv, NULL)) > 0) {
FILE *fo;
if ((fo = fdopen(fds[1], "w")) != NULL) {
int artLen = 0;
int lastLineOk = 1;
int inHeaders = 1;
int b = 0;
char *base = MBNormalize(&conn->co_ArtBuf, &artLen);
fprintf(fo, "To: %s\n", mailDest);
while (b < artLen) {
int i;
int l;
int lineOk = 1;
for (i = b; i < artLen && base[i] != '\n'; ++i)
;
if (i < artLen && base[i] == '\n')
++i;
/*
* Length of line including CR+LF
*/
l = i - b;
/*
* Remove CR+LF or LF (LF will be added back in the email)
*/
if (l > 0 && base[i-1] == '\n') {
--l;
if (l > 0 && base[i-2] == '\r')
--l;
}
/*
* Get rid of leading dot if dot-escaping
*/
if (l > 0 && base[b] == '.') {
--l;
++b;
}
/*
* Process headers, output line. The lastLineOk stuff is
* to handle header line continuation.
*/
if (inHeaders) {
if (l == 0) {
inHeaders = 0;
} else if (base[b] == ' ' || base[b] == '\t') {
lineOk = lastLineOk;
} else if (l >= 3 && !strncasecmp(base + b, "To:", 3)) {
lineOk = 0;
} else if (l >= 3 && !strncasecmp(base + b, "Cc:", 3)) {
lineOk = 0;
} else if (l >= 4 && !strncasecmp(base + b, "Bcc:", 4)) {
lineOk = 0;
} else if (l >= 5 && !strncasecmp(base + b, "Path:", 5)) {
lineOk = 0;
}
}
if (lineOk) {
fwrite(base + b, l, 1, fo);
fputc('\n', fo);
}
lastLineOk = lineOk;
b = i;
}
fclose(fo);
} else {
close(fds[1]);
}
waitpid(pid, NULL, 0);
}
}
int
CheckGroupModeration(const char *group)
{
const char *rec;
int recLen;
int r = -1;
rec = KPDBReadRecord(KDBActive, group, KP_LOCK, &recLen);
if (rec != NULL) {
int slen;
const char *s = KPDBGetField(rec, recLen, "S", &slen, NULL);
if (s) {
int i;
for (i = 0; i < slen; ++i) {
if (s[i] == 'm')
r = 0;
}
}
KPDBUnlock(KDBActive, rec);
}
return(r);
}
int
CheckValidRestrictedGroup(const char *group)
{
const char *rec;
int recLen;
int r = -1;
/* Allow posting into a (currently) nonexistent group. We will create */
if (DOpts.ReaderAutoAddToActive)
r = 0;
rec = KPDBReadRecord(KDBActive, group, KP_LOCK, &recLen);
if (rec != NULL) {
int slen;
const char *s = KPDBGetField(rec, recLen, "S", &slen, NULL);
r = 0;
if (s) {
int i;
for (i = 0; i < slen; ++i) {
if (s[i] == 'x')
r = 1;
}
}
KPDBUnlock(KDBActive, rec);
}
return(r);
}
/*
* GenerateDistribution(): generate a distribution header. At the moment
* we only generate one distribution tag, even if
* crossposting, using the earliest highest-weight
* tag found in distrib.pats.
*
* If we cannot find any matches, no Distribution:
* header is generated.
*/
void
GenerateDistribution(MBufHead *mbuf, char *newsGrps)
{
FILE *fi;
int bestWeight = -1;
char *bestDist = NULL;
if ((fi = fopen(PatLibExpand(DistribDotPatsPat), "r")) != NULL) {
char buf[256];
while (fgets(buf, sizeof(buf), fi) != NULL) {
char *weight = strtok(buf, ":\r\n");
char *pattern = weight ? strtok(NULL, ":\r\n") : NULL;
char *value = pattern ? strtok(NULL, ":\r\n") : NULL;
int i = 0;
if (value == NULL ||
weight == NULL ||
weight[0] == '#' ||
weight[0] == 0
) {
continue;
}
while (newsGrps[i]) {
int j;
char c;
for (j = i; newsGrps[j] && newsGrps[j] != ','; ++j)
;
c = newsGrps[j];
newsGrps[j] = 0;
if (WildCmp(pattern, newsGrps + i) == 0) {
if (strtol(weight, NULL, 0) > bestWeight) {
zfreeStr(&SysMemPool, &bestDist);
bestWeight = strtol(weight, NULL, 0);
bestDist = zallocStr(&SysMemPool, value);
}
}
newsGrps[j] = c;
if (newsGrps[j] != ',')
break;
i = j + 1;
}
}
fclose(fi);
}
if (bestDist) {
MBPrintf(mbuf, "Distribution: %s\r\n", bestDist);
zfreeStr(&SysMemPool, &bestDist);
}
}
syntax highlighted by Code2HTML, v. 0.9.1