640 lines
15 KiB
C
640 lines
15 KiB
C
/*****************************************************************************
|
|
*
|
|
* $Id$
|
|
* Purpose ...............: rnews function
|
|
* Remarks ...............: Most of these functions are borrowed from inn.
|
|
*
|
|
*****************************************************************************
|
|
* Copyright (C) 1997-2004
|
|
*
|
|
* Michiel Broek FIDO: 2:280/2802
|
|
* Beekmansbos 10
|
|
* 1971 BV IJmuiden
|
|
* the Netherlands
|
|
*
|
|
* This file is part of MBSE BBS.
|
|
*
|
|
* This BBS is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2, or (at your option) any
|
|
* later version.
|
|
*
|
|
* MBSE BBS is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with MBSE BBS; see the file COPYING. If not, write to the Free
|
|
* Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
*****************************************************************************/
|
|
|
|
#include "../config.h"
|
|
#include "../lib/mbselib.h"
|
|
#include "../lib/users.h"
|
|
#include "../lib/mbinet.h"
|
|
#include "../lib/mbsedb.h"
|
|
#include "../lib/msg.h"
|
|
#include "../lib/msgtext.h"
|
|
#include "rfc2ftn.h"
|
|
#include "mbfido.h"
|
|
#include "../paths.h"
|
|
#include "rnews.h"
|
|
|
|
|
|
#define UUCPBUF 10240
|
|
|
|
|
|
typedef struct _HEADER {
|
|
char *Name;
|
|
int size;
|
|
} HEADER;
|
|
typedef void *POINTER;
|
|
|
|
static char UNPACK[] = "gzip";
|
|
static HEADER RequiredHeaders[] = {
|
|
{ (char *)"Message-ID", 10 },
|
|
#define _messageid 0
|
|
{ (char *)"Newsgroups", 10 },
|
|
#define _newsgroups 1
|
|
{ (char *)"From", 4 },
|
|
#define _from 2
|
|
{ (char *)"Date", 4 },
|
|
#define _date 3
|
|
{ (char *)"Subject", 7 },
|
|
#define _subject 4
|
|
{ (char *)"Path", 4 },
|
|
#define _path 5
|
|
};
|
|
#define IS_MESGID(hp) ((hp) == &RequiredHeaders[_messageid])
|
|
#define IS_PATH(hp) ((hp) == &RequiredHeaders[_path])
|
|
#define IS_NG(hp) ((hp) == &RequiredHeaders[_newsgroups])
|
|
|
|
|
|
/*
|
|
* Some macro's
|
|
*/
|
|
#define NEW(T, c) ((T *)xmalloc((unsigned int)(sizeof (T) * (c))))
|
|
#define RENEW(p, T, c) (p = (T *)realloc((char *)(p), (unsigned int)(sizeof (T) * (c))))
|
|
#define DISPOSE(p) free((void *)p)
|
|
#define SIZEOF(array) ((int)(sizeof array / sizeof array[0]))
|
|
#define ENDOF(array) (&array[SIZEOF(array)])
|
|
#define ISWHITE(c) ((c) == ' ' || (c) == '\t')
|
|
#define caseEQn(a, b, n) (strncasecmp((a), (b), (size_t)(n)) == 0)
|
|
|
|
|
|
|
|
/*
|
|
* External variables
|
|
*/
|
|
extern int do_quiet;
|
|
extern int news_in;
|
|
extern int news_dupe;
|
|
extern int check_dupe;
|
|
extern int do_flush;
|
|
|
|
|
|
void ProcessOne(FILE *);
|
|
|
|
|
|
/*
|
|
* Find a header in an article.
|
|
*/
|
|
const char *HeaderFindMem(const char *, const int, const char *, const int);
|
|
const char *HeaderFindMem(const char *Article, const int ArtLen, const char *Header, const int HeaderLen)
|
|
{
|
|
const char *p;
|
|
|
|
for (p = Article; ; ) {
|
|
/*
|
|
* Match first character, then colon, then whitespace (don't
|
|
* delete that line -- meet the RFC!) then compare the rest
|
|
* of the word.
|
|
*/
|
|
if (HeaderLen + 1 < Article + ArtLen - p && p[HeaderLen] == ':'
|
|
&& ISWHITE(p[HeaderLen + 1]) && caseEQn(p, Header, (size_t)HeaderLen)) {
|
|
p += HeaderLen + 2;
|
|
while (1) {
|
|
for (; p < Article + ArtLen && ISWHITE(*p); p++)
|
|
continue;
|
|
if (p == Article+ArtLen)
|
|
return NULL;
|
|
else {
|
|
if (*p != '\r' && *p != '\n')
|
|
return p;
|
|
else {
|
|
/* handle multi-lined header */
|
|
if (++p == Article + ArtLen)
|
|
return NULL;
|
|
if (ISWHITE(*p))
|
|
continue;
|
|
if (p[-1] == '\r' && *p== '\n') {
|
|
if (++p == Article + ArtLen)
|
|
return NULL;
|
|
if (ISWHITE(*p))
|
|
continue;
|
|
return NULL;
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ((p = memchr(p, '\n', ArtLen - (p - Article))) == NULL ||
|
|
(++p >= Article + ArtLen) || (*p == '\n') ||
|
|
(((*p == '\r') && (++p >= Article + ArtLen)) || (*p == '\n')))
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Open up a pipe to a process with fd tied to its stdin. Return a
|
|
* descriptor tied to its stdout or -1 on error.
|
|
*/
|
|
static int StartChild(int, char *, char *[]);
|
|
static int StartChild(int fd, char *path, char *argv[])
|
|
{
|
|
int pan[2], i;
|
|
pid_t pid;
|
|
|
|
/* Create a pipe. */
|
|
if (pipe(pan) < 0) {
|
|
WriteError("%Cant pipe for %s", path);
|
|
die(MBERR_EXEC_FAILED);
|
|
}
|
|
|
|
/* Get a child. */
|
|
for (i = 0; (pid = fork()) < 0; i++) {
|
|
if (i == MAX_FORKS) {
|
|
WriteError("$Cant fork %s -- spooling", path);
|
|
return -1;
|
|
}
|
|
Syslog('m', "Cant fork %s -- waiting", path);
|
|
(void)sleep(60);
|
|
}
|
|
|
|
/* Run the child, with redirection. */
|
|
if (pid == 0) {
|
|
(void)close(pan[PIPE_READ]);
|
|
|
|
/* Stdin comes from our old input. */
|
|
if (fd != STDIN) {
|
|
if ((i = dup2(fd, STDIN)) != STDIN) {
|
|
WriteError("$Cant dup2 %d to 0 got %d", fd, i);
|
|
_exit(MBERR_EXEC_FAILED);
|
|
}
|
|
(void)close(fd);
|
|
}
|
|
|
|
/* Stdout goes down the pipe. */
|
|
if (pan[PIPE_WRITE] != STDOUT) {
|
|
if ((i = dup2(pan[PIPE_WRITE], STDOUT)) != STDOUT) {
|
|
WriteError("$Cant dup2 %d to 1 got %d", pan[PIPE_WRITE], i);
|
|
_exit(MBERR_EXEC_FAILED);
|
|
}
|
|
(void)close(pan[PIPE_WRITE]);
|
|
}
|
|
|
|
Syslog('m', "execv %s %s", MBSE_SS(path), MBSE_SS(argv[1]));
|
|
(void)execv(path, argv);
|
|
WriteError("$Cant execv %s", path);
|
|
_exit(MBERR_EXEC_FAILED);
|
|
}
|
|
|
|
(void)close(pan[PIPE_WRITE]);
|
|
(void)close(fd);
|
|
return pan[PIPE_READ];
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Wait for the specified number of children.
|
|
*/
|
|
void WaitForChildren(int i)
|
|
{
|
|
pid_t pid;
|
|
int status;
|
|
|
|
while (--i >= 0) {
|
|
pid = waitpid(-1, &status, WNOHANG);
|
|
if (pid < 0) {
|
|
if (errno != ECHILD)
|
|
WriteError("$Cant wait");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Write an article to the rejected directory.
|
|
*/
|
|
static void Reject(const char *, const char *, const char *);
|
|
static void Reject(const char *article, const char *reason, const char *arg)
|
|
{
|
|
#if defined(DO_RNEWS_SAVE_BAD)
|
|
char buff[SMBUF];
|
|
FILE *F;
|
|
int i;
|
|
#endif /* defined(DO_RNEWS_SAVE_BAD) */
|
|
|
|
WriteError(reason, arg);
|
|
#if defined(DO_RNEWS_SAVE_BAD)
|
|
// TempName(PATHBADNEWS, buff);
|
|
// if ((F = fopen(buff, "w")) == NULL) {
|
|
// syslog(L_ERROR, "cant fopen %s %m", buff);
|
|
// return;
|
|
// }
|
|
// i = strlen(article);
|
|
// if (fwrite((POINTER)article, (size_t)1, (size_t)i, F) != i)
|
|
// syslog(L_ERROR, "cant fwrite %s %m", buff);
|
|
// if (fclose(F) == EOF)
|
|
// syslog(L_ERROR, "cant close %s %m", buff);
|
|
#endif /* defined(DO_RNEWS_SAVE_BAD) */
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Process one article. Return TRUE if the article was okay; FALSE if the
|
|
* whole batch needs to be saved (such as when the server goes down or if
|
|
* the file is corrupted).
|
|
*/
|
|
static int Process(char *);
|
|
static int Process(char *article)
|
|
{
|
|
HEADER *hp;
|
|
char *p;
|
|
char *id = NULL;
|
|
FILE *fp;
|
|
|
|
/*
|
|
* Empty article?
|
|
*/
|
|
if (*article == '\0')
|
|
return TRUE;
|
|
|
|
/*
|
|
* Make sure that all the headers are there.
|
|
*/
|
|
for (hp = RequiredHeaders; hp < ENDOF(RequiredHeaders); hp++) {
|
|
if ((p = (char *)HeaderFindMem(article, strlen(article), hp->Name, hp->size)) == NULL) {
|
|
Reject(article, "bad_article missing %s", hp->Name);
|
|
return FALSE;
|
|
}
|
|
if (IS_MESGID(hp)) {
|
|
id = p;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Put the article in a temp file, all other existing functions
|
|
* did already work with tempfiles.
|
|
*/
|
|
fp = tmpfile();
|
|
fwrite(article, 1, strlen(article), fp);
|
|
ProcessOne(fp);
|
|
fclose(fp);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Read the rest of the input as an article. Just punt to stdio in
|
|
* this case and let it do the buffering.
|
|
*/
|
|
static int ReadRemainder(register int, char, char);
|
|
static int ReadRemainder(register int fd, char first, char second)
|
|
{
|
|
register FILE *F;
|
|
register char *article;
|
|
register int size;
|
|
register int used;
|
|
register int left;
|
|
register int i;
|
|
int ok;
|
|
|
|
/* Turn the descriptor into a stream. */
|
|
if ((F = fdopen(fd, "r")) == NULL) {
|
|
WriteError("$Can't fdopen %d", fd);
|
|
die(MBERR_GENERAL);
|
|
}
|
|
|
|
/* Get an initial allocation, leaving space for the \0. */
|
|
size = BUFSIZ + 1;
|
|
article = NEW(char, size + 2);
|
|
article[0] = first;
|
|
article[1] = second;
|
|
used = second ? 2 : 1;
|
|
left = size - used;
|
|
|
|
/* Read the input. */
|
|
while ((i = fread((POINTER)&article[used], (size_t)1, (size_t)left, F)) != 0) {
|
|
if (i < 0) {
|
|
WriteError("$Cant fread after %d bytes", used);
|
|
die(MBERR_GENERAL);
|
|
}
|
|
used += i;
|
|
left -= i;
|
|
if (left < SMBUF) {
|
|
size += BUFSIZ;
|
|
left += BUFSIZ;
|
|
RENEW(article, char, size);
|
|
}
|
|
}
|
|
if (article[used - 1] != '\n')
|
|
article[used++] = '\n';
|
|
article[used] = '\0';
|
|
(void)fclose(F);
|
|
|
|
ok = Process(article);
|
|
DISPOSE(article);
|
|
return ok;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Read an article from the input stream that is artsize bytes long.
|
|
*/
|
|
static int ReadBytecount(register int, int);
|
|
static int ReadBytecount(register int fd, int artsize)
|
|
{
|
|
static char *article;
|
|
static int oldsize;
|
|
register char *p;
|
|
register int left;
|
|
register int i;
|
|
|
|
/* If we haven't gotten any memory before, or we didn't get enough,
|
|
* then get some. */
|
|
if (article == NULL) {
|
|
oldsize = artsize;
|
|
article = NEW(char, oldsize + 1 + 1);
|
|
} else if (artsize > oldsize) {
|
|
oldsize = artsize;
|
|
RENEW(article, char, oldsize + 1 + 1);
|
|
}
|
|
|
|
/* Read in the article. */
|
|
for (p = article, left = artsize; left; p += i, left -= i)
|
|
if ((i = read(fd, p, left)) <= 0) {
|
|
i = errno;
|
|
WriteError("$Cant read wanted %d got %d", artsize, artsize - left);
|
|
#if 0
|
|
/* Don't do this -- if the article gets re-processed we
|
|
* will end up accepting the truncated version. */
|
|
artsize = p - article;
|
|
article[artsize] = '\0';
|
|
Reject(article, "short read (%s?)", strerror(i));
|
|
#endif /* 0 */
|
|
return TRUE;
|
|
}
|
|
if (p[-1] != '\n')
|
|
*p++ = '\n';
|
|
*p = '\0';
|
|
|
|
return Process(article);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Read a single text line; not unlike fgets(). Just more inefficient.
|
|
*/
|
|
static int ReadLine(char *, int, int);
|
|
static int ReadLine(char *p, int size, int fd)
|
|
{
|
|
char *save;
|
|
|
|
/* Fill the buffer, a byte at a time. */
|
|
for (save = p; size > 0; p++, size--) {
|
|
if (read(fd, p, 1) != 1) {
|
|
*p = '\0';
|
|
WriteError("$Cant read first line got %s", save);
|
|
die(MBERR_GENERAL);
|
|
}
|
|
if (*p == '\n') {
|
|
*p = '\0';
|
|
return TRUE;
|
|
}
|
|
}
|
|
*p = '\0';
|
|
WriteError("bad_line too long %s", save);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Unpack a single batch.
|
|
*/
|
|
static int UnpackOne(int *, int *);
|
|
static int UnpackOne(int *fdp, int *countp)
|
|
{
|
|
char buff[SMBUF];
|
|
char *cargv[4];
|
|
int artsize;
|
|
int i;
|
|
int gzip = 0;
|
|
int HadCount;
|
|
int SawCunbatch;
|
|
|
|
*countp = 0;
|
|
for (SawCunbatch = FALSE, HadCount = FALSE; ; ) {
|
|
/* Get the first character. */
|
|
if ((i = read(*fdp, &buff[0], 1)) < 0) {
|
|
WriteError("$cant read first character");
|
|
return FALSE;
|
|
}
|
|
if (i == 0)
|
|
break;
|
|
|
|
if (buff[0] == 0x1f)
|
|
gzip = 1;
|
|
else if (buff[0] != RNEWS_MAGIC1)
|
|
/* Not a batch file. If we already got one count, the batch
|
|
* is corrupted, else read rest of input as an article. */
|
|
return HadCount ? FALSE : ReadRemainder(*fdp, buff[0], '\0');
|
|
|
|
/* Get the second character. */
|
|
if ((i = read(*fdp, &buff[1], 1)) < 0) {
|
|
WriteError("$Cant read second character");
|
|
return FALSE;
|
|
}
|
|
if (i == 0)
|
|
/* A one-byte batch? */
|
|
return FALSE;
|
|
|
|
/* Check second magic character. */
|
|
/* gzipped ($1f$8b) or compressed ($1f$9d) */
|
|
if (gzip && ((buff[1] == (char)0x8b) || (buff[1] == (char)0x9d))) {
|
|
cargv[0] = (char *)"gzip";
|
|
cargv[1] = (char *)"-d";
|
|
cargv[2] = NULL;
|
|
lseek(*fdp, (long) 0, 0); /* Back to the beginning */
|
|
*fdp = StartChild(*fdp, (char*)_PATH_GZIP, cargv);
|
|
if (*fdp < 0)
|
|
return FALSE;
|
|
(*countp)++;
|
|
SawCunbatch = TRUE;
|
|
continue;
|
|
}
|
|
if (buff[1] != RNEWS_MAGIC2)
|
|
return HadCount ? FALSE : ReadRemainder(*fdp, buff[0], buff[1]);
|
|
|
|
/* Some kind of batch -- get the command. */
|
|
if (!ReadLine(&buff[2], (int)(sizeof buff - 3), *fdp))
|
|
return FALSE;
|
|
|
|
if (strncmp(buff, "#! rnews ", 9) == 0) {
|
|
artsize = atoi(&buff[9]);
|
|
if (artsize <= 0) {
|
|
WriteError("Bad_line bad count %s", buff);
|
|
return FALSE;
|
|
}
|
|
HadCount = TRUE;
|
|
if (ReadBytecount(*fdp, artsize))
|
|
continue;
|
|
return FALSE;
|
|
}
|
|
|
|
if (HadCount)
|
|
/* Already saw a bytecount -- probably corrupted. */
|
|
return FALSE;
|
|
|
|
if (strcmp(buff, "#! cunbatch") == 0) {
|
|
Syslog('m', "Compressed newsbatch");
|
|
if (SawCunbatch) {
|
|
WriteError("Nested_cunbatch");
|
|
return FALSE;
|
|
}
|
|
cargv[0] = UNPACK;
|
|
cargv[1] = (char *)"-d";
|
|
cargv[2] = NULL;
|
|
*fdp = StartChild(*fdp, (char *)_PATH_GZIP, cargv);
|
|
if (*fdp < 0)
|
|
return FALSE;
|
|
(*countp)++;
|
|
SawCunbatch = TRUE;
|
|
continue;
|
|
}
|
|
|
|
WriteError("bad_format unknown command %s", buff);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
void NewsUUCP(void)
|
|
{
|
|
int fd = STDIN, i, rc;
|
|
|
|
Syslog('+', "Processing UUCP newsbatch");
|
|
IsDoing((char *)"UUCP Batch");
|
|
|
|
if (!do_quiet) {
|
|
mbse_colour(10, 0);
|
|
printf("Process UUCP Newsbatch\n");
|
|
}
|
|
|
|
rc = UnpackOne(&fd, &i);
|
|
WaitForChildren(i);
|
|
Syslog('+', "End of UUCP batch, rc=%d", rc);
|
|
do_flush = TRUE;
|
|
|
|
if (!do_quiet)
|
|
printf("\r \r");
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Process one newsarticle.
|
|
*/
|
|
void ProcessOne(FILE *fp)
|
|
{
|
|
char *p, *fn, *buf, *gbuf, *mbuf, *group, *groups[25];
|
|
int i, nrofgroups;
|
|
unsigned long crc;
|
|
|
|
buf = calloc(UUCPBUF, sizeof(char));
|
|
fn = calloc(PATH_MAX, sizeof(char));
|
|
|
|
/*
|
|
* Find newsgroups names in article.
|
|
*/
|
|
rewind(fp);
|
|
nrofgroups = 0;
|
|
mbuf = NULL;
|
|
gbuf = NULL;
|
|
while (fgets(buf, UUCPBUF, fp)) {
|
|
if (!strncasecmp(buf, "Newsgroups: ", 12)) {
|
|
gbuf = xstrcpy(buf+12);
|
|
Striplf(gbuf);
|
|
strtok(buf, " ");
|
|
while ((group = strtok(NULL, ",\n"))) {
|
|
if (SearchMsgsNews(group)) {
|
|
Syslog('m', "Add group %s (%s)", msgs.Newsgroup, msgs.Tag);
|
|
groups[nrofgroups] = xstrcpy(group);
|
|
nrofgroups++;
|
|
} else {
|
|
Syslog('-', "Newsgroup %s doesn't exist", group);
|
|
}
|
|
}
|
|
}
|
|
if (!strncasecmp(buf, "Message-ID: ", 12)) {
|
|
/*
|
|
* Store the Message-ID without the < > characters.
|
|
*/
|
|
mbuf = xstrcpy(buf+13);
|
|
mbuf[strlen(mbuf)-2] = '\0';
|
|
Syslog('m', "Message ID \"%s\"", printable(mbuf, 0));
|
|
}
|
|
}
|
|
|
|
if (nrofgroups == 0) {
|
|
WriteError("No newsgroups found: %s", gbuf);
|
|
} else if (mbuf == NULL) {
|
|
WriteError("No valid Message-ID found");
|
|
} else {
|
|
IsDoing("Article %d", (news_in + 1));
|
|
for (i = 0; i < nrofgroups; i++) {
|
|
Syslog('m', "Process %s", groups[i]);
|
|
p = xstrcpy(mbuf);
|
|
p = xstrcat(p, groups[i]);
|
|
crc = str_crc32(p);
|
|
if (check_dupe && CheckDupe(crc, D_NEWS, CFG.nntpdupes)) {
|
|
news_dupe++;
|
|
news_in++;
|
|
Syslog('+', "Duplicate article \"%s\" in group %s", mbuf, groups[i]);
|
|
} else {
|
|
if (SearchMsgsNews(groups[i])) {
|
|
rfc2ftn(fp, NULL);
|
|
}
|
|
}
|
|
free(groups[i]);
|
|
}
|
|
}
|
|
|
|
if (mbuf)
|
|
free(mbuf);
|
|
if (gbuf)
|
|
free(gbuf);
|
|
|
|
free(buf);
|
|
free(fn);
|
|
}
|
|
|
|
|
|
|