/*-
 * Copyright (c) 1996
 *      David Parsons & Dean Edmonds.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by David Parsons
 *      (orc@pell.chi.il.us) and Dean Edmonds (deane@io.org)
 * 4. The authors names may not be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * automod: moderate articles via a bot.
 */
#include "automod.h"
#include <stdio.h>
#include <string.h>
#include <mailer.h>
#include <time.h>
#include <syslog.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include "dbmsetup.h"
#include "config-vars.h"


/*
 * header lines that are of some importance
 */
struct _header {
    char *line;
    int lsize;
    char *value;
    int flag;
#define HDR_OPTIONAL    0
#define HDR_REQUIRED    01
#define HDR_DISCARD     02
#define HEADER(name,flag)   { name, 0, 0, flag }
} headers[] = {
    HEADER("From:",            HDR_REQUIRED),
#define FROM        (headers)
    HEADER("Newsgroups:",      HDR_REQUIRED),
#define NEWSGROUPS  (headers+1)
    HEADER("References:",      HDR_OPTIONAL),
#define REFERENCES  (headers+2)
    HEADER("Subject:",         HDR_REQUIRED),
#define SUBJECT     (headers+3)
    HEADER("Path:",            HDR_OPTIONAL),
#define PATH        (headers+4)
    HEADER("Date:",            HDR_REQUIRED),
    HEADER("Message-Id:",      HDR_OPTIONAL),
#define MESSAGE_ID  (headers+6)
    HEADER("Sender:",          HDR_DISCARD),
#define SENDER      (headers+7)
    HEADER("X-Reply-To:",      HDR_OPTIONAL),
#define XREPLYTO    (headers+8)
    HEADER("Reply-To:",        HDR_OPTIONAL),
#define REPLY_TO    (headers+9)
    HEADER("Control:",        HDR_OPTIONAL),
#define CONTROL     (headers+10)
    HEADER("Followups-To:",   HDR_OPTIONAL),
#define FOLLOWUPS_TO (headers+11)
    HEADER("Keywords:",        HDR_OPTIONAL),
    HEADER("To:",              HDR_DISCARD),
    HEADER("Received:",        HDR_DISCARD),
    HEADER("Approved:",        HDR_DISCARD),
    HEADER("From ",            HDR_DISCARD),
    HEADER("Xref:",	       HDR_DISCARD),
    HEADER(">From ",           HDR_DISCARD),
#if USE_INEWS
    HEADER("NNTP-Posting-Host:",      HDR_DISCARD),
#endif
};
#define NR_HEADERS  (sizeof headers/sizeof headers[0])

/*
** Macros to do error-checking arglist construction
*/
#define XADDARG(arg,siz)    (addarg(&argc,&argv,arg,siz) == -1)\
                    ? critical_error("building exec list", errno)\
                    : 0
#define XADDSTR(arg)    XADDARG(arg,-1)

FILE *log = NULL;       /* the incoming mail log */
long headermask;        /* missing headers, for reject(..., DENY_HEADERS) */
char *critical_message; /* error messages for reject(..., DENY_CRITICAL) */
int   critical_errno;
Boolean rejecting = False; /* set nonzero when we're rejecting an article, so
                           ** panics won't cause infinite looping
                           */
char *fromAddress = 0;  /* From: line on the mail message */
char *genericFrom = 0;  /* Sender's addr with first domain element removed */
Boolean delurk_flag = False;  /* subject is prefixed with the Keyword */

static int lineNumber = 0;      /* Current line number in config file. */
static Boolean endTagFound = False;
static char *quoteChars = NULL;
static int quoteCharsLen = 0;
static char *ungottenLine = NULL;


/*
 * duplicate a string, lowercasing it as we go.
 */
static char *
strlwrdup(char *originalstring)
{
    char *tmp;

    if ((tmp = strdup(originalstring)) != (char*)0)
        strlwr(tmp);
    return tmp;
} /* strlwrdup */


static void
cleanupAndExit(int exitCode)
{
    if (LogSubmissions) fclose(log);

    thread_unlock();
    exit(exitCode);
}


/*
** addpending() adds someone to the PENDING database
**
** A pending database entry consists of an 8 character date in YYYYMMDD
** format, followed by a blank, followed by a 1 char status (K = has
** supplied keyword at some point, P = pending with no keyword seen yet),
** followed by a blank, followed by the Subject line of the sender's most
** recent (failed) attempt.
*/
addpending(char *name, char status, char *subject)
{
    GDBM_FILE pending;

    SYSLOG(LOG_DEBUG, "addpending('%s', '%s')", name, subject);

    if (pending = gdbm_open(pathname(PENDING), 0, GDBM_WRCREAT, 0660, 0)) {
        datum key, value;

        key.dptr = strlwrdup(name);
        key.dsize = strlen(name);

        value = formatPending(formatCurrentDate(), status, subject);

        gdbm_store(pending, key, value, GDBM_REPLACE);
        gdbm_close(pending);
    }
} /* addpending */

/*
 * droppending() drops someone from the PENDING database
 */
droppending(char *name)
{
    GDBM_FILE pending;

    SYSLOG(LOG_DEBUG, "droppending('%s')", name);

    if (pending = gdbm_open(pathname(PENDING), 0, GDBM_WRITER, 0660, 0)) {
        datum key;

        key.dptr  = strlwrdup(name);
        key.dsize = strlen(name);

        gdbm_delete(pending, key);
        gdbm_close(pending);
    }
} /* droppending */


/*
** Return the STATUS field from the poster's record in the Pending
** database.  If the poster is not in the Pending database, then return
** STATUS_NEW.
**
** Author: Dean Edmonds, 24 Jan 1997
*/
static char
getPendingState(char *address)
{
    GDBM_FILE pending;
    char pendingState = STATUS_NEW;

    if (pending = gdbm_open(pathname(PENDING), 0, GDBM_READER, 0660, 0)) {
        datum key, value;

        key.dptr = strlwrdup(address);
        key.dsize = strlen(address);
        value = gdbm_fetch(pending, key);

        gdbm_close(pending);

        parsePending(value, NULL, NULL, &pendingState, NULL);
    }

    return pendingState;
}


/*
** Returns True if the poster in the Approved database, False otherwise.
**
** Author: Dean Edmonds, 24 Jan 1997
*/
static Boolean
isApproved(char *fromAddress, char *genericFromAddress)
{
    GDBM_FILE approve;

    if (approve = gdbm_open(pathname(APPROVE), 0, GDBM_READER, 0660, 0)) {
        datum key, value;

        key.dptr = strlwrdup(fromAddress);
        key.dsize = strlen(fromAddress);
        value = gdbm_fetch(approve, key);

        /*
        ** If we didn't find the user by his exact address, then let's try
        ** using his generic address. (See retrieve() for more info on
        ** generic addresses.)
        */
        if (genericFromAddress && ((value.dsize == 0) || (value.dptr == NULL)))
        {
            key.dptr = strlwrdup(genericFromAddress);
            key.dsize = strlen(genericFromAddress);
            value = gdbm_fetch(approve, key);
        }

        gdbm_close(approve);

        if ((value.dsize > 0) && value.dptr) return True;
    }

    return False;  
}


/*
** Returns a pointer to the start of the Keyword (or OldKeyword) in
** `subjectLine', or NULL if no keywords are found.
**
** Author: Dean Edmonds, 24 Jan 1997
*/
static char *
findKeyword(char *subjectLine, int *keywordLen)
{
    char *line;
    char *keywordPtr = NULL;
    static char *lowercaseKeyword = NULL;
    static char *lowercaseOldKeyword = NULL;

    line = strlwrdup(subjectLine);

    if (lowercaseKeyword == NULL) lowercaseKeyword = strlwrdup(Keyword);

    if ((keywordPtr = strstr(line, lowercaseKeyword)) == NULL) {
        /*
        ** We didn't find the current keyword. The group might have
        ** recently switched to a new keyword, so if there is an
        ** old keyword defined, check for it, too.
        */
        if (OldKeyword != NULL) {
            if (lowercaseOldKeyword == NULL)
                lowercaseOldKeyword = strlwrdup(OldKeyword);

            keywordPtr = strstr(line, lowercaseOldKeyword);

            if (keywordLen) *keywordLen = strlen(OldKeyword);
        }
    }
    else if (keywordLen != NULL) {
        *keywordLen = strlen(Keyword);
    }

    /*
    ** Convert keywordPtr from a pointer to the keyword in our local copy
    ** of the subject line to a pointer to the keyword in the original
    ** subject line that was passed in to us.
    */
    if (keywordPtr) keywordPtr = subjectLine + (keywordPtr - line);

    free(line);

    return keywordPtr;
}


/*
** Get a line of input, write it to the log file, strip off the trailing
** newline and get rid of leading quote characters.
**
** Author: Dean Edmonds, 23 Jan 1997
*/
static char *
getLine(FILE *input, FILE *log)
{
    char *line;
    char buff[1024];

    /*
    ** If there is an ungotten line hanging around, use it.
    */
    if (ungottenLine) {
        line = ungottenLine;
        ungottenLine = NULL;
        return line;
    }

    /*
    ** Read a line from the input stream.
    */
    if (fgets(buff, sizeof(buff), input)) {
        int lineLen;

        /*
        ** Write it to the log file.
        */
        if (log) fputs(buff, log);

        /*
        ** Get rid of the trailing newline, if there is one.
        */
        lineLen = strlen(buff);

        if (buff[lineLen-1] == '\n') buff[--lineLen] = '\0';

        /*
        ** If we know what the quote characters are, strip them off the line.
        */
        line = buff;

        if ((quoteCharsLen > 0)
        &&      (strncmp(line, quoteChars, quoteCharsLen) == 0)) {
            line = buff + quoteCharsLen;
        }

        /*
        ** Return a copy of the line to the caller.
        */
        return strdup(line);
    }

    return NULL;
}


/*
** Stick a line back onto the input stream being used by getLine().
**
** Note that only one line may be pushed back at any given time and that
** there is no differentiation made between input streams. It's cheap,
** I know, but it's all we need.
**
** Author: Dean Edmonds, 19 Nov 1996
*/
static void
ungetLine(char *line)
{
    ungottenLine = line;
}


/*
** Given the initial portion of a message header line, return the entire
** header by appending any continuation lines. (Continuation lines for
** headers begin with blanks or tabs.)
**
** Note that `headerSoFar' is destroyed by this call and should no longer
** be referenced by the caller.
**
** Author: Dean Edmonds, 19 Nov 1996
*/
static char *
getRestOfHeader(char *headerSoFar, FILE *input, FILE *log)
{
    char *nextLine;

    /*
    ** Get the next line of text.
    */
    if (nextLine = getLine(input, log)) {
        /*
        ** If it begins with a blank or tab then it is a continuation of
        ** the current header.
        */
        if ((*nextLine == ' ') || (*nextLine == '\t')) {
            char *p;

            /*
            ** Skip over any leading whitespace.
            */
            p = nextLine + strspn(nextLine, " \t");

            /*
            ** Resize `headerSoFar' to make room for the additional text.
            */
            headerSoFar =
                    realloc(headerSoFar, strlen(headerSoFar) + strlen(p) + 1);

            /*
            ** Append this new line to the header so far.
            */
            strcat(headerSoFar, p);
            free(nextLine);
        }
        else {
            /*
            ** This is not part of the current header, so stick it back onto
            ** the input stream.
            */
            ungetLine(nextLine);
        }
    }

    return headerSoFar;
}


/*
** Read in the next available header block and write it out to the
** article file.
**
** NOTE: We should eventually make sure that we split long header lines up
**       into continued lines when writing them out, otherwise we could end
**       up overflowing the news limits somewhere down the line.
*/
static int
getheaders(FILE *messageFile, FILE *finalArticle)
{
    char *line, *str;
    Boolean headersFound = False;
    register i;
    struct _header *p;

    /*
    ** We're looking for a new set of headers, so throw away any previous
    ** set that we might have found.
    */
    for (i=0; i<NR_HEADERS; i++) {
        p = &headers[i];

        if (p->value) {
            free(p->value);
            p->value = 0;
        }
    }

    /*
    ** Anything that we might have written out as part of the final
    ** article is worthless as well, so get rid of it, too.
    */
    fflush(finalArticle);
    rewind(finalArticle);
    ftruncate(fileno(finalArticle), 0);

    /*
    ** Start grabbing headers. We'll stop when we hit the empty line at the
    ** end of a block of headers.
    */
    while (line = getLine(messageFile, NULL)) {
        /*
        ** If we have an empty line, and we've found at least one valid header,
        ** then we are done with headers.
        */
        if ((*line == '\0') && headersFound) break;

        /*
        ** Is this a header that we recognize?
        */
        for (i=0; i<NR_HEADERS; i++) {
            p = &headers[i];

            if (strncasecmp(p->line, line, p->lsize) == 0) {
                headersFound = True;

                /*
                ** We've got the first line of a header which may be continued
                ** on several lines, so grab the entire thing.
                */
                line = getRestOfHeader(line, messageFile, NULL);

                if (p->value) free(p->value);

                p->value = strdup(Trim(line + p->lsize));

                if (p->flag & HDR_DISCARD) break;

                if (p == PATH) {
                    fprintf(finalArticle, "%s there.is.no.cabal\n",
                                PATH->line, PATH->value);
                }
                else if (p == SUBJECT) {
                    char *keywordPtr;
                    int   keywordLen;

                    /*
                    ** Strip the Keyword out of the Subject line, if it is
                    ** there.
                    */
                    if (keywordPtr = findKeyword(p->value, &keywordLen)) {
                        while (*keywordPtr = *(keywordPtr + keywordLen))
                            keywordPtr++;
                    }

                    fprintf(finalArticle, "%s %s\n", p->line, p->value);
                }
                else
                    fprintf(finalArticle, "%s\n", line);

                break;
            }
        }

        /*
        ** If we didn't recognize the line, and it really is a header line
        ** (i.e. begins with a string followed by a colon and a space) then
        ** assume that it is a customized header and just copy it straight
        ** through. Otherwise, toss it away.
        */
        if (i >= NR_HEADERS) {
            /*
            ** The header may be continued on multiple lines, so get the
            ** whole thing.
            */
            line = getRestOfHeader(line, messageFile, NULL);

            /*
            ** In order for this to be a valid header, it must begin with a
            ** blank-free string, followed by a quote and a blank. So find
            ** the first colon and the first blank and make sure that the
            ** former immediately preceds the latter.
            */
            if (index(line, ' ') == index(line, ':') + 1)
                fprintf(finalArticle, "%s\n", line);
        }

        free(line);
    }

    if (line) free(line);

    return headersFound;
} /* getheaders */


/*
 * complete(bits) checks to see if all the required headers have been read
 */
static int
complete(long *bits)
{
    register i;
    register struct _header *p;

    *bits = 0;

    for (i=0; i<NR_HEADERS;i++) {
        p = &headers[i];
        if ((p->flag & HDR_REQUIRED) && (p->value == (char*)0))
            (*bits) |= (1<<i);
    }
    return (*bits) ? 0 : 1;
}


/*
** Extract from the current set of headers an address appropriate for
** replying to.
**
** Author: Dean Edmonds, 19 Nov 1996
*/
static char *
extractFromAddress()
{
    char *fromAddr = NULL;

    /*
    ** The `Sender' line is supposed to supercede the `From' line, but
    ** in almost all cases it ends up pointing to either the same person
    ** as the From line, in which case we might as well use the From,
    ** or else it points to some layered protocol or gateway through which
    ** the mail is being passed, in which case we don't _want_ to use the
    ** Sender line because mail sent there will never get back to the
    ** author.
    **
    ** Besides, all of the mail utilities ignore the Sender line, so why
    ** shouldn't we?
    **
    ** So the code below only obeys Reply-To and From.
    */
    if (REPLY_TO->value)
        fromAddr = getfromaddress(strdup(REPLY_TO->value));
    else if (XREPLYTO->value) {
        fromAddr = getfromaddress(strdup(XREPLYTO->value));
        free(fromAddr);

        if (FROM->value)
            fromAddr = getfromaddress(strdup(FROM->value));
        else
            fromAddr = (char*)0;
    }
    else if (FROM->value)
        fromAddr = getfromaddress(strdup(FROM->value));

    return fromAddr;
}


/*
** Send the poster a message listing the headers that were missing, then
** clean up and exit.
**
** Author: Dean Edmonds, 29 Dec 1996
*/
static void
badHeaders_and_exit(long missingMask, FILE *copyOfMsg, FILE *finalArticle)
{
    fclose(finalArticle);
    fflush(copyOfMsg);

    if (fromAddress) {
        /*
        ** If this is not an approved poster, but zie did supply a keyword,
        ** then put zir into the Pending database so with a status that
        ** tells us not to insist on a Keyword in subsequent attempts.
        */
        if (!isApproved(fromAddress, genericFrom) && delurk_flag)
            addpending(fromAddress, STATUS_KEYWORD, "");

        /*
        ** Send out the rejection message and exit.
        */
        SYSLOG(LOG_ERR, "Improperly formatted article header");
        headermask = missingMask;
        reject(copyOfMsg, fromAddress, DENY_HEADERS);

        cleanupAndExit(0);
    }

    /*
    ** We don't have a From address, so there's no one to send mail to, so
    ** just log the problem and exit.
    */
    if (LogSubmissions) fputs("@BAD----Incomplete headers\n", log);

    fclose(copyOfMsg);
    printf("Unparsable FROM address?");
    SYSLOG(LOG_ERR, "Unparsable FROM address?");

    cleanupAndExit(1);
}


/*
** This routine performs three functions:
**
** 1) Determines if the user has specified the Keyword in _any_ of the
**    Subject lines of his message.
**
** 2) Copies the entire incoming message into the log file.
**
** 3) Extracts the original message from a reply and copies it to the
**    `processedOutput' file. If this message is not a reply then it simply
**    copies the entire message to the `processedOutput' file.
**
** Author: Dean Edmonds, 23 Jan 1997
*/
static Boolean
extractMsgFromReply(FILE *log, FILE *processedOutput)
{
    Boolean foundKeyword = False;
    char *str, *line;


    while (line = getLine(stdin, log)) {
        /*
        ** Check for the the starting tagline. (We only care about its
        ** first occurrence.)
        */
        if ((quoteChars == NULL) && (str = strstr(line, START_TAGLINE))) {
            /*
            ** Aha! We've found a starting tagline. That means that this
            ** message must be a response to an earlier rejection that we
            ** sent out and the text after the tagline should contain the
            ** corrected article (possibly including mail headers).
            **
            ** So discard the tagline and everything before it since all
            ** that we need should be contained between the starting and
            ** ending taglines.
            */
            fflush(processedOutput);
            rewind(processedOutput);
            ftruncate(fileno(processedOutput), 0);

            /*
            ** The poster's mailer may have inserted quoting characters at
            ** the start of each line of included text. If so then we would
            ** like to strip them off of all subsequent lines so that we
            ** correctly recognize headers lines and so that the article
            ** won't have all that unnecessary extra quoting at the start
            ** of each line.
            **
            ** We identify the quoting chars by looking at everything that
            ** appears at the start of the line containing the tag string.
            ** getLine() will then strip this sequence of chars from all
            ** further lines.
            */
            *str = '\0';
            quoteChars = line;
            quoteCharsLen = strlen(quoteChars);
            continue;
        }

        /*
        ** If we've seen the starting tagline, and this is the matching end
        ** tagline, then stop processing the incoming message since we now
        ** have all of it that we will ever care about.
        */
        if ((quoteChars != NULL)
        &&      (strncmp(line, END_TAGLINE, sizeof(END_TAGLINE) - 1) == 0)) {
            free(line);
            break;
        }

        /*
        ** Check to see if this is a Subject line containing the Keyword.
        ** Note that we do this even on Subject lines which lie outside the
        ** taglines and will therefore be discarded. This is because the
        ** Keyword is a special case: we allow the user to specify if in
        ** any Subject line, including the Subject of zir mail, simply to
        ** make life easier on zir.
        */
        if (strncasecmp(line, SUBJECT->line, SUBJECT->lsize) == 0) {
            char *str1, *lowercaseKeyword;

            /*
            ** Header lines may be continued on multiple lines, so let's
            ** get the whole thing together.
            */
            line = getRestOfHeader(line, stdin, log);

            if (findKeyword(line, NULL)) foundKeyword = True;
        }

        /*
        ** Write the line to the processed output file.
        */
        fprintf(processedOutput, "%s\n", line);
        free(line);
    }

    /*
    ** Copy the remainder of the message to the log file.
    */
    while (line = getLine(stdin, log)) {
        free(line);
    }

    /*
    ** Rewind the processed output file so that others can start working
    ** with it.
    */
    fflush(processedOutput);
    rewind(processedOutput);

    /*
    ** We've now stripped as much quoting as we plan to from the input, so
    ** let's make sure that subsequent passes don't strip out any more.
    */
    free(quoteChars);
    quoteChars = NULL;
    quoteCharsLen = 0;

    /*
    ** Let the caller know if we found the Keyword.
    */
/*    return foundKeyword;*/
	return True;
}


/*
 * spool an incoming piece of mail, picking out interesting headers.
 */
static FILE *
retrieve()
{
    char *p1, *p2, *line;
    FILE *copyOfMsg;
    FILE *finalArticle;
    register c;
    int error;
    long missing;

    if (LogSubmissions) {
        if ((log = fopen(pathname(LOG), "a")) == (FILE*)0) {
            LogSubmissions = False;
            critical_error("opening logfile", errno);
        }
    }

    if (((copyOfMsg = tmpfile()) == (FILE*)0)
    ||      ((finalArticle = tmpfile()) == (FILE*)0)) {
        critical_error("preparing workfiles", errno);
    }

    /*
    ** There are two basic scenarios:
    **
    ** 1) The user is sending us a brand new article. If this is the case,
    **    then it will consist of either a combined news/mail header
    **    followed by the body of the article, or else a mail header,
    **    followed by a separate news header, followed by the body of the
    **    article.
    **
    ** 2) The user is replying to a rejection message that we sent zir in
    **    response to an earlier attempt to post an article. If this is the
    **    case then there will be a mail header, possibly followed by a
    **    copy of the mail header from the rejection message, maybe
    **    followed by a bunch of blather that we sent zir explaining the
    **    rejection, followed by a special start tagline, followed by the
    **    mail message zie sent us on the previous attempt to post followed
    **    by a special end tagline, followed by potentially more crap like
    **    sig file stuff.
    **
    **    We've told the user to make all of zir corrections to the part
    **    between the taglines, so if we throw away everything before the
    **    starting tagline and after the ending tagline, then we'll have
    **    everything we need and will be able to process what's left the
    **    same as we would scenario (1) above.
    **
    ** So the first step is to convert any scenario 2 messages into
    ** scenario 1's.
    */
    delurk_flag = extractMsgFromReply(log, copyOfMsg);

    /*
    ** Okay, so now we having nothing but Scenario 1 messages.
    **
    ** As mentioned earlier, Scenario 1 messages come in two possible
    ** formats:
    **
    ** A) There is one, combined set of mail/news headers, followed by the
    **    body of the news article.
    **
    ** B) There is a set of mail headers, followed by a separate set of
    **    news headers, followed by the body of the article. In this case,
    **    RFC 850 says that the `From:' address in the mail header should
    **    point back to the daemon which created the mail message while the
    **    `From:' address in the article header points back to the author
    **    of the article. Although not all systems obey the first half of
    **    this requirement, many do, so we must be sure to get the author's
    **    address from the second set of headers, if we want to send any
    **    messages back to zir.
    **
    ** Regardless of which format we have here, our first step is to grab
    ** the first set of headers from the message.
    */
    if (!getheaders(copyOfMsg, finalArticle)) {
        /*
        ** No headers at all?? Nothing to do but log an error and exit since
        ** we don't even have an address to mail a failure message to.
        */
        critical_error("reading mail header", errno);
    }

    fromAddress = extractFromAddress();

    /*
    ** Does this set of headers include the news-specific headers as well?
    */
    if (!complete(&missing)) {
        char *tmpFromAddress;

        /*
        ** The mail header did not contain the news headers. That means
        ** that we _should_ have format B here, with a separate set of news
        ** headers after this, so go looking for them.
        */
        if (!getheaders(copyOfMsg, finalArticle)) {
            /*
            ** There's no second set of headers at all, so let's assume
            ** that we have format A with incomplete headers. Send the
            ** author a message letting him know what's missing.
            */
            badHeaders_and_exit(missing, copyOfMsg, finalArticle);
        }

        /*
        ** Okay, we have a second set of headers, so this must be format B.
        ** In that case, all of our communications with the author should
        ** go to the From address in the second set of headers.
        */
        if (tmpFromAddress = extractFromAddress()) {
            if (fromAddress) free(fromAddress);

            fromAddress = tmpFromAddress;
        }

        /*
        ** Does the second set of headers contain everything that we need?
        */
        if (!complete(&missing)) {
            /*
            ** Nope. Tell the author what's missing.
            */
            badHeaders_and_exit(missing, copyOfMsg, finalArticle);
        }
    }

    /*
    ** Some users are connected routers that can connect them through a
    ** number of different machines within the same domain. Thus, the
    ** user has no control over the first element in zir domain name and
    ** may end up with addresses such as `me@node1.iou.edu',
    ** `me@node2.iou.edu', etc. This means that zie will look to automod like
    ** a first time poster each time zie hits a new node.
    **
    ** To get around this, the moderator can manually replace the user's
    ** address in the approval database with one containing a `generic'
    ** domain name. I.e. one in which the first element of the domain name
    ** is `*'. Thus `me@node1.iou.edu' and `me@node2.iou.edu' would both
    ** have the same generic address of `me@*.iou.edu'.
    **
    ** If automod cannot find a match against the exact address then
    ** it will then attempt a match against the generic address. (Note that
    ** this won't help those people for whom the second or later element of
    ** their FQDN changes, but hopefully they are a small and docile group.)
    **
    ** So we have to create a generic address from the sender's specific
    ** address just in case automod needs it.
    */
    genericFrom = strdup(fromAddress);

    if (p1 = index(genericFrom, '@')) {
        if (*++p1) {
            *p1++ = '*';

            if (p2 = index(p1, '.')) {
                /*
                ** We would like to simply do strcpy(p1, p2) below, but
                ** strcpy() is not guaranteed to work on overlapping strings.
                ** So the trickery below copies the corresponding text from
                ** the original fromAddress string.
                */
                strcpy(p1, fromAddress + (p2 - genericFrom));
            }
            else {
                *p1 = '\0';
            }
        }
    }

    /* Supply a message-id if the article doesn't include one
     */
    if (MESSAGE_ID->value == (char*)0) {
        time_t now;

        time(&now);
        fprintf(finalArticle, "Message-ID: <%lx-%s>\n", now, Approval);
    }

    /*
    ** Supply a path if the article doesn't include one
    */
    if (PATH->value == (char*)0)
        fprintf(finalArticle, "Path: there.is.no.cabal\n");

    /*
    ** Supply a Newsgroups: line if the article doesn't include one
    */
    if (NEWSGROUPS->value == (char*)0)
        fprintf(finalArticle, "Newsgroups: %s\n", Newsgroup);
   
    putc('\n', finalArticle);

    /*
    ** Copy the rest of the message to the article file.
    */
    while (line = getLine(copyOfMsg, NULL)) {
        fprintf(finalArticle, "%s\n", line);
        free(line);
    }

    fclose(copyOfMsg);
    fflush(finalArticle);
    rewind(finalArticle);

    return finalArticle;
} /* retrieve */


/*
 * dopost() writes the file to the RNEWS program.
 */
dopost(FILE *ff)
{
    FILE *rnews;
    register c;
    int rc;
    struct stat info;

    if (rnews = popen(RNEWS, "w")) {
        register c;

        rewind(ff);
        fprintf(rnews, "Approved: %s\n", Approval);

        while ((c=getc(ff)) != EOF)
            putc(c, rnews);

        if ((rc = pclose(rnews)) != 0)
            critical_error(RNEWS " failed", errno);

        if (LogSubmissions) fputs("@OK ----\n", log);
    }
    else
        critical_error("posting article", errno);
} /* dopost */


/*
 * Posts an article and adds the poster's email address to the autoapprove
 * database, giving the comment as the entry reason.
 */
firstPost(FILE *ff, char *poster, char *comment)
{
    GDBM_FILE approve;

    SYSLOG(LOG_DEBUG, "post(ff, '%s', '%s')", poster, comment);
    dopost(ff);

    if (approve = gdbm_open(pathname(APPROVE), 0, GDBM_WRCREAT, 0660, 0)) {
        datum key, value;

        key.dptr = strlwrdup(poster);
        key.dsize = strlen(poster);

        value = formatApproved(formatCurrentDate(), comment);

        gdbm_store(approve, key, value, GDBM_REPLACE);
        gdbm_close(approve);
    }
}


/*
 * post_message() dumps a message from a file out to a FILE*, for
 * posting custom welcome and reject messages.
 *
 * post_message() calls expandText() which does some special macro
 * substitutions in the text stream. See expandText.c for more details.
 */
int
post_message(FILE *to, char *file)
{
    register c;
    FILE *from;

    if (from = fopen(file, "r")) {
        expandText(from, to);
        fclose(from);
        return 1;
    }
    return 0;
} /* post_message */


/* 
 * welcome_message() mails a welcome or success message to the poster
 *
 *      doSuccessMsg == True    - send a success message
 *      doSuccessMsg == False   - send a welcome message
 */
void
welcome_message(char *from, Boolean doSuccessMsg)
{
    char **argv = malloc(1);
    int argc = 0;
    int pip[2];                 /* IPC between server and sendmail */
    register c;
    int rc;
    int i;
    FILE *pipeline;
    char *p;
    pid_t pid;
    int found_message=0;

    SYSLOG(LOG_DEBUG, "welcome_message('%s')", from);
    rejecting++;

    /*
    ** If we've been asked to send a success message, but the group is
    ** configured not to send success messages, then do nothing.
    */
    if (doSuccessMsg && !SendSuccessMsg) return;

    /*
    ** Build the sendmail command line.
    */
    XADDSTR("sendmail");
    XADDSTR("-i");
    XADDSTR(from);
    XADDSTR((char*)0);

    if (pipe(pip) != 0)
        critical_error("creating pipe", errno);

    if ((pid=launch(SENDMAIL, argv, pip[0], EOF, EOF)) >= 0) {
        /* pipe everything to the child, then die */

        if ((pipeline = fdopen(pip[1], "w")) != (FILE *)0) {
            fprintf(pipeline, "From: %s (%s automoderation robot)\n",
                                SubmissionAddr, Newsgroup);

            if (doSuccessMsg) {
                fprintf(
                    pipeline,
                    "Subject: Your article has been posted to %s\n",
                    Newsgroup
                );

                fprintf(pipeline, "To: %s\n", from);
                fprintf(pipeline, "\n");

                if (!post_message(pipeline, pathname(SUCCESS_MSG))) {
                    /*
                    ** There is no customized message for this group, so let's
                    ** do something nice and generic.
                    */
                    fprintf(pipeline,
"This message is confirmation that you have made your first successful\n"
"post to the %s newsgroup.\n"
"\n"
"This is the only time that you will receive this message so you don't have\n"
"to worry about your mailbox filling up with messages from the group's\n"
"moderation software whenever you post. Note also that you will not need\n"
"the keyword in your subsequent posts to the group.\n"
"\n"
"If you have any problems or questions, you may email them to the group's\n"
"(human) administrator at %s\n"
"\n"
"Have fun!\n",
                         Newsgroup, AdminAddr);
                }
            }
            else {
                fprintf(pipeline, "Subject: Welcome to %s\n", Newsgroup);
                fprintf(pipeline, "To: %s\n", from);
                fprintf(pipeline, "\n");

                if (!post_message(pipeline, pathname(WELCOME_MSG))) {
                    /*
                    ** There is no customized message for this group, so let's
                    ** do something nice and generic.
                    */
                    fprintf(pipeline,
"Welcome to %s.\n"
"\n"
"This message is confirmation that your very first article to the group\n"
"has reached the group's moderation software. You will be sent a separate\n"
"message confirming that your article has actually been posted, or explaining\n"
"what was wrong if it did not get posted.\n"
"\n"
"The reason for these separate messages is simply to make it easier for the\n"
"group's administrators to track down any problems which might occur. You\n"
"will not receive this message with future submissions to the group.\n",
                            Newsgroup);

                    /*
                    ** If the poster doesn't know the keyword, then we'd
                    ** better tell zir here because there's no place else
                    ** that zie can find out.
                    */
                    if (!delurk_flag) {
                        fprintf(pipeline,
"\n"
"If your article is rejected, it may be because of a missing keyword, which\n"
"you need in the Subject line of your very first post to the group. That\n"
"keyword is currently `%s' (without the quotes).\n"
"\n"
"The reason for requiring this first-post keyword is that it helps to\n"
"eliminate commercial postings and `spam' from the newsgroup.\n",
                                Keyword);
                    }

                    fprintf(pipeline,
"\n"
"If you have any problems or questions, you may email them to the group's\n"
"(human) administrator at %s\n"
"\n"
"Have fun!\n",
                         AdminAddr);
                }
            }

            if (ferror(pipeline))
                critical_error("writing to " SENDMAIL, errno);

            fclose(pipeline);

            waitpid(pid, &rc, 0);
            /*exit(WEXITSTATUS(rc));*/
        }
        else
            critical_error("writing to " SENDMAIL, errno);
    }
    else
        critical_error("launching " SENDMAIL, errno);
} /* welcome_message */


/*
 * reject() returns the message to the sender with some short blurb
 * explaining why the message was not allowed.
 */
reject(FILE *ff, char *poster, enum Denial denial_code)
{
    char **argv = malloc(1);
    int argc = 0;
    int pip[2];                 /* IPC between server and sendmail */
    register c;
    int rc;
    int i;
    FILE *pipeline;
    char *p;
    pid_t pid;
    int found_message=0;
    char *whatToFix = NULL;
    char msg[300];

    SYSLOG(LOG_DEBUG, "reject(ff, '%s', %d)", poster, denial_code);
    rejecting++;
	
    /*
    ** Build the sendmail command line.
    */
    XADDSTR("sendmail");
    XADDSTR("-i");
    XADDSTR(poster);
    XADDSTR((char*)0);

    if (pipe(pip) != 0)
        critical_error("creating pipe", errno);

    if ((pid=launch(SENDMAIL, argv, pip[0], EOF, EOF)) >= 0) {
        /* pipe everything to the child, then die */

        if ((pipeline = fdopen(pip[1], "w")) != (FILE *)0) {
            fprintf(pipeline, "From: %s (%s automoderation robot)\n",
                                SubmissionAddr, Newsgroup);
            fprintf(pipeline, "Subject: Your post titled [%s]\n",
                                SUBJECT->value);
            fprintf(pipeline, "To: %s\n", poster);
            fprintf(pipeline, "\n");

            /* attempt to send messages from a file out, and if that
             * fails fall back on a tinned message
             */
            switch (denial_code) {
            case DENY_CROSSPOST:
                if (LogSubmissions) fprintf(log, "@BAD----Crosspost violation\n");
                found_message = post_message(pipeline, pathname(CROSSPOST_MSG));
                break;
	    case DENY_FOLLOWUPS:
               if (LogSubmissions) fprintf(log, "@BAD----Followup violation\n");
                found_message = post_message(pipeline, pathname(FOLLOWUPS_MSG));
                break;
            case DENY_FIRSTPOST:
                if (LogSubmissions) fprintf(log, "@BAD----No keyword\n");
                found_message = post_message(pipeline, pathname(FIRSTPOST_MSG));
                break;
            case DENY_REPOST:
                if (LogSubmissions) fprintf(log, "@BAD----No keyword\n");
                found_message = post_message(pipeline, pathname(REPOST_MSG));
                break;
            case DENY_CRITICAL:
                if (LogSubmissions) fprintf(log, "@BAD----Critical error\n");
                break;
            case DENY_HEADERS:
                if (LogSubmissions) fprintf(log, "@BAD----Incomplete headers\n");
                break;
            }

            if (!found_message) {
                fprintf(pipeline,
    "Your post has been returned by the %s\n"
    "moderation software because ", Newsgroup);

                switch (denial_code) {
                    case DENY_CROSSPOST:
                        fprintf(pipeline,
    "it violates the group's crossposting rules.\n");
                        sprintf(msg,
    "remove all newsgroups\n"
    "other than %s from the Newsgroups: line in your\n"
    "article, which is between the `%s' and `%s'\n"
    "markers below.\n",
                                Newsgroup, START_TAG_ABBREV, END_TAG_ABBREV);
                        whatToFix = msg;
                        break;
		    case DENY_FOLLOWUPS:
			                         fprintf(pipeline,
    "it violates the group's crossposting/followups rules.\n");
                        sprintf(msg,
    "remove all newsgroups\n"
    "over 4 in number from the Followups-To: line in your\n"
    "article, which is between the `%s' and `%s'\n"
    "markers below.\n",
                        Newsgroup, START_TAG_ABBREV, END_TAG_ABBREV);
                        whatToFix = msg;
                        break;
                    case DENY_FIRSTPOST:
                    case DENY_REPOST:
                        fprintf(pipeline,
    "it appears to be your first post to the group\n"
    "and does not contain the first post keyword in the Subject line.\n");

                        sprintf(msg,
    "add the keyword `%s'\n"
    "(without the quotes) to the end of the Subject line in to your article,\n"
    "which is between the `%s' and `%s'\n"
    "markers below.\n",
                                Keyword, START_TAG_ABBREV, END_TAG_ABBREV);
                        whatToFix = msg;
                        break;

                    case DENY_HEADERS:
                        fprintf(pipeline,
    "it was missing the following header line(s):\n\n");

                        for (i=0; i<NR_HEADERS; i++) {
                            if (headermask & (1<<i))
                                fprintf(pipeline, "\t%s\n", headers[i].line);
                        }

                        whatToFix =
    "add the missing header\n"
    "line(s) to your article, which is between the `" START_TAG_ABBREV "' and\n"
    "'" END_TAG_ABBREV "' markers below.\n";
                        break;

                    case DENY_CRITICAL:
/*                        fprintf(pipeline,
    "a system error happened while it was being posted.\n"
    "Please contact %s about it,\n"
    "with a full copy of this message, including this note:\n"
    "\n"
    "\tWhen: %s\n", AdminAddr, critical_message);
                        if (critical_errno) {
                            fprintf(pipeline, "\tError %d\n", critical_errno);
                            fprintf(pipeline, "\t      %s\n",
                                                    strerror(critical_errno));
                        }
*/
                        break;
                }

                /*
                ** In every case except a critical error, the user can repost
                ** simply by correcting the problem and replying, so let zir
                ** know this. The test below works because `ff' is always
                ** NULL when this routine is called with DENY_CRITICAL.
                */
                if (ff) {
                    fprintf(pipeline,
    "\n"
    "If you still wish to post this article to %s,\n"
    "then simply reply to this message with this entire message as part of the\n"
    "reply. The only change that you need make is to %s\n"
    "Don't worry about deleting the text before the `%s'\n"
    "or after the `%s' as it will be ignored anyway. If your\n"
    "mailer `quotes' messages when replying to them (i.e. adds characters\n"
    "to the original message) you don't have to fix that either: the\n"
    "the moderation software will strip off the quoting automatically,\n"
    "and use your original message.\n",
                            Newsgroup,
                            whatToFix,
                            START_TAG_ABBREV, END_TAG_ABBREV);
                }
            }

            /*
            ** Dump out a copy of either the final article or the user's
            ** original message (whichever we were passed) bracketed by
            ** the tag lines so we will recognize it later on.
            */
            if (ff) {
                fprintf(pipeline, "\n%s\n", START_TAGLINE);

                rewind(ff);

                while ((c=getc(ff)) != EOF)
                    putc(c, pipeline);

                fprintf(pipeline, "%s\n", END_TAGLINE);
            }

            if (ferror(pipeline))
                critical_error("writing to " SENDMAIL, errno);

            fclose(pipeline);

            waitpid(pid, &rc, 0);
        }
        else
            critical_error("writing to " SENDMAIL, errno);
    }
    else
        critical_error("launching " SENDMAIL, errno);
}


/*
 * critical_error() is the all-purpose panic and inform everyone
 * error routine.  It writes an error report on stdout, for sendmail
 * to return to the sender, and it also writes a error message to
 * the server moderator for their (hopefully) immediate attention
 */
critical_error(char *message, int errornumber)
{
    FILE *mail;
    char sendmailCmd[300];
/*
    sprintf(sendmailCmd, "%s %s", SENDMAIL, AdminAddr);
    mail = popen(sendmailCmd, "w");

    SYSLOG(LOG_CRIT, errornumber ? "%s %s" : "%s",
                     message, strerror(errornumber));
    if (mail) {
        fprintf(mail, "From: %s (auto moderation bot)\n", Newsgroup);
        fprintf(mail, "Subject: IT'S THE APOCOLYPSE!\n\n");
        fprintf(mail, "We lost an incoming article: %s\n", message);
        if (errornumber) {
            fprintf(mail, "\terror %d\n", errornumber);
            fprintf(mail, "\t      %s\n", strerror(errornumber));
        }
        fprintf(mail, "\n-- the computer\n");
        pclose(mail);
    }
*/
    /* If we've picked up a From: address, send email there; otherwise
     * the errors will just vanish into the void
     */
    if (fromAddress && !rejecting) {
        critical_message = message;
        critical_errno   = errornumber;
      /*  reject(NULL, fromAddress, DENY_CRITICAL);*/
    }
    else if (LogSubmissions) {
        fprintf(log, "@BAD----Critical error\n");
    }

    cleanupAndExit(1);
}


/*
 * automod, in mortal flesh
 */
main(int argc, char **argv)
{
    FILE *article;
    datum key, value;
    char *p;
    int i;
    Boolean approved;
    char pendingState;

    setregid(getegid(),getegid());
    setreuid(geteuid(),geteuid());

    if (argc == 1) {
        Newsgroup = DEFAULT_NEWSGROUP;
    }
    else if ((argc == 3) && (strcmp(argv[1], "-g") == 0)) {
        Newsgroup = strdup(argv[2]);
    }
    else {
        fprintf(
            stderr,
            "automod v%d.%d, Copyright 1996, David Parsons & Dean Edmonds\n\n",
            MAJOR_VERSION, MINOR_VERSION
        );
        fprintf(stderr, "Usage: %s [-g newsgroup]\n",
                argv[0]);
        exit(1);
    }

    /*
    ** Get the configuration info.
    */
    parseConfig(Newsgroup, True);

    for (i=0; i<NR_HEADERS; i++)
        headers[i].lsize = strlen(headers[i].line);

    thread_lock(Newsgroup);
    openlog(Newsgroup, 0, LOG_NEWS);

    article = retrieve();

    /* summarily ignore mail from mailer-daemons */
    if (strcmp(fromAddress, MAILER) == 0) {
        if (LogSubmissions) fprintf(log, "@BAD----Mail from daemon\n");

        cleanupAndExit(0);
    }

    /*
    ** Check for a Control message.
    */
    if (CONTROL->value) {
        /*
        ** If it is a cancel message, then accept it with no further
        ** validation. We do this because Control messages often do not
        ** include the poster's Reply-To addr, so we cannot reliably do
        ** address validation.
        */
        if (strncasecmp(CONTROL->value, "cancel", 6) == 0) {
            dopost(article);

            if (LogSubmissions) fputs("@OK ----Cancel msg\n", log);
        }
        /*
        ** Any other control message we just discard, since it shouldn't
        ** be coming through automod anyway.
        */
        else {
            if (LogSubmissions)
                fputs("@BAD----Control msg was not a Cancel\n", log);
        }

        cleanupAndExit(0);
    }

    /*
    ** Is this an approved or pending poster?
    */
    approved = isApproved(fromAddress, genericFrom);
    pendingState = getPendingState(fromAddress);

    /*
    ** If the poster is not already approved, but has specified a Keyword
    ** this time around, then add zir to the pending database. The reason
    ** that we do this now is so that if the article fails some subsequent
    ** check, we will still have a record that the poster used the Keyword,
    ** so we won't insist on having the Keyword on subsequent attempts.
    */
    if (!approved && delurk_flag)
        addpending(fromAddress, STATUS_KEYWORD, SUBJECT->value);

    /*
    ** Validate the Newsgroups line.
    */
    if (NEWSGROUPS->value) {
        GDBM_FILE crosspost;
        int crossposted_to=0;
	int followups_to=0;
        /*
        ** Make sure that the message is only being crossposted to approved
        ** groups.
        */
        if (crosspost = gdbm_open(pathname(XPOST), 0, GDBM_READER, 0, 0)) {
            for (p=strtok(NEWSGROUPS->value,","); p; p=strtok((char*)0, ",")) {
                crossposted_to++;
                if (strcmp(p, Newsgroup) == 0)
                    continue;
                key.dptr = strlwrdup(p);
                key.dsize = strlen(p);

                value = gdbm_fetch(crosspost, key);

                /*
                ** If `AllowCrossposts' is true then the crosspost database
                ** contains the blackball list of those newsgroups which
                ** cannot crosspost to ours. All other groups are allowed.
                **
                ** If `AllowCrossposts' if false then the crosspost database
                ** contains the approved list of those newsgroups which
                ** are allowed to crosspost to ours. All other groups are
                ** disallowed.
                **
                ** So, if AllowCrossposts is true and this group is in the
                ** database, or if AllowCrossposts is false and this group
                ** is not in the database, then reject the message.
                */
                if ((AllowCrossposts && (value.dsize != 0) && (value.dptr != 0))
                ||      (!AllowCrossposts &&
                                (value.dsize == 0 || value.dptr == 0))) {
                    reject(article, fromAddress, DENY_CROSSPOST);
                    cleanupAndExit(0);
                }
            }

            gdbm_close(crosspost);

		/* daves follups code here */

	for (p=strtok(FOLLOWUPS_TO->value,","); p; p=strtok((char*)0, ",")) {
                followups_to++;
                if (strcmp(p, Newsgroup) == 0)
                    continue;
	}
            /*
            ** Let's also avoid over-enthusiasm, even for _valid_ crosspost
            ** groups.
            */
            if (crossposted_to > MaxGroups) {
                reject(article, fromAddress, DENY_CROSSPOST);
                cleanupAndExit(0);
            }
	    
	    if (followups_to > MaxGroups) {
		reject(article, fromAddress, DENY_FOLLOWUPS);
		cleanupAndExit(0);
	    }
        }
    }

    /*
    ** If the poster is on the approved list, post zir article and exit.
    */
    if (approved) {
        SYSLOG(LOG_DEBUG, "Approved from %s", fromAddress);
        dopost(article);
        cleanupAndExit(0);
    }

    SYSLOG(LOG_DEBUG, "%s not in %s", fromAddress, pathname(APPROVE));

    /*
    ** If the poster supplied the Keyword, either this time around or on
    ** one of zir previous failed attempts, then let's post zir article and
    ** finally approve zir.
    */
    if (delurk_flag || (pendingState == STATUS_KEYWORD)) {
        droppending(fromAddress);

        /*
        ** Post the article. If this is the poster's first attempt, then
        ** send zir the welcome message as well.
        */
        if (pendingState == STATUS_NEW) {
            firstPost(article, fromAddress, "Got it right on first attempt.");
            welcome_message(fromAddress, False);
        }
        else
            firstPost(article, fromAddress, "Required multiple attempts.");

        /*
        ** Send the poster a success message to let zir know that zie
        ** finally got it right!
        */
        welcome_message(fromAddress, True);

        cleanupAndExit(0);
    }

    /*
    ** This poster has never given us a Keyword, so add zir to the Pending
    ** database and reject the article.
    **
    ** If this is the poster's first time, also send out the welcome
    ** message.
    */
    addpending(fromAddress, STATUS_PENDING, SUBJECT->value);

    if (pendingState == STATUS_NEW) {
        welcome_message(fromAddress, False);
        reject(article, fromAddress, DENY_FIRSTPOST);
    }
    else
        reject(article, fromAddress, DENY_REPOST);

    cleanupAndExit(0);
} /* main */
