chiark / gitweb /
REORG Delete everything that's not innduct or build system or changed for innduct
[innduct.git] / backends / innxmit.c
diff --git a/backends/innxmit.c b/backends/innxmit.c
deleted file mode 100644 (file)
index 475ce63..0000000
+++ /dev/null
@@ -1,1457 +0,0 @@
-/*  $Id: innxmit.c 6716 2004-05-16 20:26:56Z rra $
-**
-**  Transmit articles to remote site.
-**  Modified for NNTP streaming: 1996-01-03 Jerry Aguirre
-*/
-
-#include "config.h"
-#include "clibrary.h"
-#include "portable/socket.h"
-#include "portable/time.h"
-#include <ctype.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <setjmp.h>
-#include <signal.h>
-#include <syslog.h>
-#include <sys/stat.h>
-#include <sys/uio.h>
-
-/* Needed on AIX 4.1 to get fd_set and friends. */
-#ifdef HAVE_SYS_SELECT_H
-# include <sys/select.h>
-#endif
-
-#include "inn/history.h"
-#include "inn/innconf.h"
-#include "inn/messages.h"
-#include "inn/qio.h"
-#include "inn/timer.h"
-#include "inn/wire.h"
-#include "libinn.h"
-#include "nntp.h"
-#include "paths.h"
-#include "storage.h"
-
-#define OUTPUT_BUFFER_SIZE     (16 * 1024)
-
-/* Streaming extensions to NNTP.  This extension removes the lock-step
-** limitation of conventional NNTP.  Article transfer is several times
-** faster.  Negotiated and falls back to old mode if receiver refuses.
-*/
-
-/* max number of articles that can be streamed ahead */
-#define STNBUF 32
-
-/* Send "takethis" without "check" if this many articles were
-** accepted in a row.
-*/
-#define STNC 16
-
-/* typical number of articles to stream  */
-/* must be able to fopen this many articles */
-#define STNBUFL (STNBUF/2)
-
-/* number of retries before requeueing to disk */
-#define STNRETRY 5
-
-struct stbufs {                /* for each article we are procesing */
-       char *st_fname;         /* file name */
-       char *st_id;            /* message ID */
-       int   st_retry;         /* retry count */
-       int   st_age;           /* age count */
-       ARTHANDLE *art;         /* arthandle to read article contents */
-       int   st_hash;          /* hash value to speed searches */
-       long  st_size;          /* article size */
-};
-static struct stbufs stbuf[STNBUF]; /* we keep track of this many articles */
-static int stnq;       /* current number of active entries in stbuf */
-static long stnofail;  /* Count of consecutive successful sends */
-
-static int TryStream = true;   /* Should attempt stream negotation? */
-static int CanStream = false;  /* Result of stream negotation */
-static int DoCheck   = true;   /* Should check before takethis? */
-static char modestream[] = "mode stream";
-static char modeheadfeed[] = "mode headfeed";
-static long retries = 0;
-static int logRejects = false ;  /* syslog the 437 responses. */
-
-
-
-/*
-** Syslog formats - collected together so they remain consistent
-*/
-static char    STAT1[] =
-       "%s stats offered %lu accepted %lu refused %lu rejected %lu missing %lu accsize %.0f rejsize %.0f";
-static char    STAT2[] = "%s times user %.3f system %.3f elapsed %.3f";
-static char    GOT_BADCOMMAND[] = "%s rejected %s %s";
-static char    REJECTED[] = "%s rejected %s (%s) %s";
-static char    REJ_STREAM[] = "%s rejected (%s) %s";
-static char    CANT_CONNECT[] = "%s connect failed %s";
-static char    CANT_AUTHENTICATE[] = "%s authenticate failed %s";
-static char    IHAVE_FAIL[] = "%s ihave failed %s";
-
-static char    CANT_FINDIT[] = "%s can't find %s";
-static char    CANT_PARSEIT[] = "%s can't parse ID %s";
-static char    UNEXPECTED[] = "%s unexpected response code %s";
-
-/*
-**  Global variables.
-*/
-static bool            AlwaysRewrite;
-static bool            Debug;
-static bool            DoRequeue = true;
-static bool            Purging;
-static bool            STATprint;
-static bool            HeadersFeed;
-static char            *BATCHname;
-static char            *BATCHtemp;
-static char            *REMhost;
-static double          STATbegin;
-static double          STATend;
-static FILE            *BATCHfp;
-static int             FromServer;
-static int             ToServer;
-static struct history  *History;
-static QIOSTATE                *BATCHqp;
-static sig_atomic_t    GotAlarm;
-static sig_atomic_t    GotInterrupt;
-static sig_atomic_t    JMPyes;
-static jmp_buf         JMPwhere;
-static char            *REMbuffer;
-static char            *REMbuffptr;
-static char            *REMbuffend;
-static unsigned long   STATaccepted;
-static unsigned long   STAToffered;
-static unsigned long   STATrefused;
-static unsigned long   STATrejected;
-static unsigned long   STATmissing;
-static double          STATacceptedsize;
-static double          STATrejectedsize;
-
-
-/* Prototypes. */
-static ARTHANDLE *article_open(const char *path, const char *id);
-static void article_free(ARTHANDLE *);
-
-
-/*
-**  Return true if the history file has the article expired.
-*/
-static bool
-Expired(char *MessageID) {
-    return !HISlookup(History, MessageID, NULL, NULL, NULL, NULL);
-}
-
-
-/*
-**  Flush and reset the site's output buffer.  Return false on error.
-*/
-static bool
-REMflush(void)
-{
-    int                i;
-
-    if (REMbuffptr == REMbuffer) return true; /* nothing buffered */
-    i = xwrite(ToServer, REMbuffer, (int)(REMbuffptr - REMbuffer));
-    REMbuffptr = REMbuffer;
-    return i < 0 ? false : true;
-}
-
-/*
-**  Return index to entry matching this message ID.  Else return -1.
-**  The hash is to speed up the search.
-**  the protocol.
-*/
-static int
-stindex(char *MessageID, int hash) {
-    int i;
-
-    for (i = 0; i < STNBUF; i++) { /* linear search for ID */
-       if ((stbuf[i].st_id) && (stbuf[i].st_id[0])
-        && (stbuf[i].st_hash == hash)) {
-           int n;
-
-           if (strcasecmp(MessageID, stbuf[i].st_id)) continue;
-
-           /* left of '@' is case sensitive */
-           for (n = 0; (MessageID[n] != '@') && (MessageID[n] != '\0'); n++) ;
-           if (strncmp(MessageID, stbuf[i].st_id, n)) continue;
-           else break; /* found a match */
-       }
-    }
-    if (i >= STNBUF) i = -1;  /* no match found ? */
-    return (i);
-}
-
-/* stidhash(): calculate a hash value for message IDs to speed comparisons */
-static int
-stidhash(char *MessageID) {
-    char       *p;
-    int                hash;
-
-    hash = 0;
-    for (p = MessageID + 1; *p && (*p != '>'); p++) {
-       hash <<= 1;
-       if (isascii((int)*p) && isupper((int)*p)) {
-           hash += tolower(*p);
-       } else {
-           hash += *p;
-       }
-    }
-    return hash;
-}
-
-/* stalloc(): save path, ID, and qp into one of the streaming mode entries */
-static int
-stalloc(char *Article, char *MessageID, ARTHANDLE *art, int hash) {
-    int i;
-
-    for (i = 0; i < STNBUF; i++) {
-       if ((!stbuf[i].st_fname) || (stbuf[i].st_fname[0] == '\0')) break;
-    }
-    if (i >= STNBUF) { /* stnq says not full but can not find unused */
-       syslog(L_ERROR, "stalloc: Internal error");
-       return (-1);
-    }
-    if ((int)strlen(Article) >= SPOOLNAMEBUFF) {
-       syslog(L_ERROR, "stalloc: filename longer than %d", SPOOLNAMEBUFF);
-       return (-1);
-    }
-    /* allocate buffers on first use.
-    ** If filename ever is longer than SPOOLNAMEBUFF then code will abort.
-    ** If ID is ever longer than NNTP_STRLEN then other code would break.
-    */
-    if (!stbuf[i].st_fname)
-        stbuf[i].st_fname = xmalloc(SPOOLNAMEBUFF);
-    if (!stbuf[i].st_id)
-        stbuf[i].st_id = xmalloc(NNTP_STRLEN);
-    strlcpy(stbuf[i].st_fname, Article, SPOOLNAMEBUFF);
-    strlcpy(stbuf[i].st_id, MessageID, NNTP_STRLEN);
-    stbuf[i].art = art;
-    stbuf[i].st_hash = hash;
-    stbuf[i].st_retry = 0;
-    stbuf[i].st_age = 0;
-    stnq++;
-    return i;
-}
-
-/* strel(): release for reuse one of the streaming mode entries */
-static void
-strel(int i) {
-       if (stbuf[i].art) {
-            article_free(stbuf[i].art);
-           stbuf[i].art = NULL;
-       }
-       if (stbuf[i].st_id) stbuf[i].st_id[0] = '\0';
-       if (stbuf[i].st_fname) stbuf[i].st_fname[0] = '\0';
-       stnq--;
-}
-
-/*
-**  Send a line to the server, adding the dot escape and \r\n.
-*/
-static bool
-REMwrite(char *p, int i, bool escdot) {
-    int        size;
-
-    /* Buffer too full? */
-    if (REMbuffend - REMbuffptr < i + 3) {
-       if (!REMflush())
-           return false;
-       if (REMbuffend - REMbuffer < i + 3) {
-           /* Line too long -- grow buffer. */
-           size = i * 2;
-            REMbuffer = xrealloc(REMbuffer, size);
-           REMbuffend = &REMbuffer[size];
-       }
-    }
-
-    /* Dot escape, text of the line, line terminator. */
-    if (escdot && (*p == '.'))
-       *REMbuffptr++ = '.';
-    memcpy(REMbuffptr, p, i);
-    REMbuffptr += i;
-    *REMbuffptr++ = '\r';
-    *REMbuffptr++ = '\n';
-
-    return true;
-}
-
-
-/*
-**  Print transfer statistics, clean up, and exit.
-*/
-static void
-ExitWithStats(int x)
-{
-    static char                QUIT[] = "quit";
-    double             usertime;
-    double             systime;
-
-    if (!Purging) {
-       REMwrite(QUIT, strlen(QUIT), false);
-       REMflush();
-    }
-    STATend = TMRnow_double();
-    if (GetResourceUsage(&usertime, &systime) < 0) {
-       usertime = 0;
-       systime = 0;
-    }
-
-    if (STATprint) {
-       printf(STAT1, REMhost, STAToffered, STATaccepted, STATrefused,
-               STATrejected, STATmissing, STATacceptedsize, STATrejectedsize);
-       printf("\n");
-       printf(STAT2, REMhost, usertime, systime, STATend - STATbegin);
-       printf("\n");
-    }
-
-    syslog(L_NOTICE, STAT1, REMhost, STAToffered, STATaccepted, STATrefused,
-               STATrejected, STATmissing, STATacceptedsize, STATrejectedsize);
-    syslog(L_NOTICE, STAT2, REMhost, usertime, systime, STATend - STATbegin);
-    if (retries)
-       syslog(L_NOTICE, "%s %lu Streaming retries", REMhost, retries);
-
-    if (BATCHfp != NULL && unlink(BATCHtemp) < 0 && errno != ENOENT)
-        syswarn("cannot remove %s", BATCHtemp);
-    sleep(1);
-    SMshutdown();
-    HISclose(History);
-    exit(x);
-    /* NOTREACHED */
-}
-
-
-/*
-**  Close the batchfile and the temporary file, and rename the temporary
-**  to be the batchfile.
-*/
-static void
-CloseAndRename(void)
-{
-    /* Close the files, rename the temporary. */
-    if (BATCHqp) {
-       QIOclose(BATCHqp);
-       BATCHqp = NULL;
-    }
-    if (ferror(BATCHfp)
-     || fflush(BATCHfp) == EOF
-     || fclose(BATCHfp) == EOF) {
-       unlink(BATCHtemp);
-        syswarn("cannot close %s", BATCHtemp);
-       ExitWithStats(1);
-    }
-    if (rename(BATCHtemp, BATCHname) < 0) {
-        syswarn("cannot rename %s", BATCHtemp);
-       ExitWithStats(1);
-    }
-}
-
-
-/*
-**  Requeue an article, opening the temp file if we have to.  If we get
-**  a file write error, exit so that the original input is left alone.
-*/
-static void
-Requeue(const char *Article, const char *MessageID)
-{
-    int fd;
-
-    /* Temp file already open? */
-    if (BATCHfp == NULL) {
-        fd = mkstemp(BATCHtemp);
-        if (fd < 0) {
-            syswarn("cannot create a temporary file");
-            ExitWithStats(1);
-        }
-        BATCHfp = fdopen(fd, "w");
-        if (BATCHfp == NULL) {
-            syswarn("cannot open %s", BATCHtemp);
-            ExitWithStats(1);
-        }
-    }
-
-    /* Called only to get the file open? */
-    if (Article == NULL)
-       return;
-
-    if (MessageID != NULL)
-       fprintf(BATCHfp, "%s %s\n", Article, MessageID);
-    else
-       fprintf(BATCHfp, "%s\n", Article);
-    if (fflush(BATCHfp) == EOF || ferror(BATCHfp)) {
-        syswarn("cannot requeue %s", Article);
-       ExitWithStats(1);
-    }
-}
-
-
-/*
-**  Requeue an article then copy the rest of the batch file out.
-*/
-static void
-RequeueRestAndExit(char *Article, char *MessageID) {
-    char       *p;
-
-    if (!AlwaysRewrite
-     && STATaccepted == 0 && STATrejected == 0 && STATrefused == 0
-     && STATmissing == 0) {
-        warn("nothing sent -- leaving batchfile alone");
-       ExitWithStats(1);
-    }
-
-    warn("rewriting batch file and exiting");
-    if (CanStream) {   /* streaming mode has a buffer of articles */
-       int i;
-
-       for (i = 0; i < STNBUF; i++) {    /* requeue unacknowledged articles */
-           if ((stbuf[i].st_fname) && (stbuf[i].st_fname[0] != '\0')) {
-               if (Debug)
-                   fprintf(stderr, "stbuf[%d]= %s, %s\n",
-                           i, stbuf[i].st_fname, stbuf[i].st_id);
-               Requeue(stbuf[i].st_fname, stbuf[i].st_id);
-               if (Article == stbuf[i].st_fname) Article = NULL;
-               strel(i); /* release entry */
-           }
-       }
-    }
-    Requeue(Article, MessageID);
-
-    for ( ; BATCHqp; ) {
-       if ((p = QIOread(BATCHqp)) == NULL) {
-           if (QIOtoolong(BATCHqp)) {
-                warn("skipping long line in %s", BATCHname);
-               QIOread(BATCHqp);
-               continue;
-           }
-           if (QIOerror(BATCHqp)) {
-                syswarn("cannot read %s", BATCHname);
-               ExitWithStats(1);
-           }
-
-           /* Normal EOF. */
-           break;
-       }
-
-       if (fprintf(BATCHfp, "%s\n", p) == EOF
-        || ferror(BATCHfp)) {
-            syswarn("cannot requeue %s", p);
-           ExitWithStats(1);
-       }
-    }
-
-    CloseAndRename();
-    ExitWithStats(1);
-}
-
-
-/*
-**  Clean up the NNTP escapes from a line.
-*/
-static char *
-REMclean(char *buff) {
-    char       *p;
-
-    if ((p = strchr(buff, '\r')) != NULL)
-       *p = '\0';
-    if ((p = strchr(buff, '\n')) != NULL)
-       *p = '\0';
-
-    /* The dot-escape is only in text, not command responses. */
-    return buff;
-}
-
-
-/*
-**  Read a line of input, with timeout.  Also handle \r\n-->\n mapping
-**  and the dot escape.  Return true if okay, *or we got interrupted.*
-*/
-static bool
-REMread(char *start, int size) {
-    static int         count;
-    static char                buffer[BUFSIZ];
-    static char                *bp;
-    char               *p;
-    char               *q;
-    char               *end;
-    struct timeval     t;
-    fd_set             rmask;
-    int                        i;
-    char               c;
-
-    if (!REMflush())
-       return false;
-
-    for (p = start, end = &start[size - 1]; ; ) {
-       if (count == 0) {
-           /* Fill the buffer. */
-    Again:
-           FD_ZERO(&rmask);
-           FD_SET(FromServer, &rmask);
-           t.tv_sec = 10 * 60;
-           t.tv_usec = 0;
-           i = select(FromServer + 1, &rmask, NULL, NULL, &t);
-           if (GotInterrupt)
-               return true;
-           if (i < 0) {
-               if (errno == EINTR)
-                   goto Again;
-               return false;
-           }
-           if (i == 0 || !FD_ISSET(FromServer, &rmask))
-               return false;
-           count = read(FromServer, buffer, sizeof buffer);
-           if (GotInterrupt)
-               return true;
-           if (count <= 0)
-               return false;
-           bp = buffer;
-       }
-
-       /* Process next character. */
-       count--;
-       c = *bp++;
-       if (c == '\n')
-           break;
-       if (p < end)
-           *p++ = c;
-    }
-
-    /* We know we got \n; if previous char was \r, turn it into \n. */
-    if (p > start && p < end && p[-1] == '\r')
-       p[-1] = '\n';
-    *p = '\0';
-
-    /* Handle the dot escape. */
-    if (*p == '.') {
-       if (p[1] == '\n' && p[2] == '\0')
-           /* EOF. */
-           return false;
-       for (q = &start[1]; (*p++ = *q++) != '\0'; )
-           continue;
-    }
-    return true;
-}
-
-
-/*
-**  Handle the interrupt.
-*/
-static void
-Interrupted(char *Article, char *MessageID) {
-    warn("interrupted");
-    RequeueRestAndExit(Article, MessageID);
-}
-
-
-/*
-**  Returns the length of the headers.
-*/
-static int
-HeadersLen(ARTHANDLE *art, int *iscmsg) {
-    const char *p;
-    char       lastchar = -1;
-
-    /* from nnrpd/article.c ARTsendmmap() */
-    for (p = art->data; p < (art->data + art->len); p++) {
-       if (*p == '\r')
-           continue;
-       if (*p == '\n') {
-           if (lastchar == '\n') {
-               if (*(p-1) == '\r')
-                   p--;
-               break;
-           }
-           if (*(p + 1) == 'C' && strncasecmp(p + 1, "Control: ", 9) == 0)
-               *iscmsg = 1;
-       }
-       lastchar = *p;
-    }
-    return (p - art->data);
-}
-
-
-/*
-**  Send a whole article to the server.
-*/
-static bool
-REMsendarticle(char *Article, char *MessageID, ARTHANDLE *art) {
-    char       buff[NNTP_STRLEN];
-
-    if (!REMflush())
-       return false;
-    if (HeadersFeed) {
-       struct iovec vec[3];
-       char buf[20];
-       int iscmsg = 0;
-       int len = HeadersLen(art, &iscmsg);
-
-       vec[0].iov_base = (char *) art->data;
-       vec[0].iov_len = len;
-       /* Add 14 bytes, which maybe will be the length of the Bytes header */
-       snprintf(buf, sizeof(buf), "Bytes: %lu\r\n",
-                 (unsigned long) art->len + 14);
-       vec[1].iov_base = buf;
-       vec[1].iov_len = strlen(buf);
-       if (iscmsg) {
-           vec[2].iov_base = (char *) art->data + len;
-           vec[2].iov_len = art->len - len;
-       } else {
-           vec[2].iov_base = (char *) "\r\n.\r\n";
-           vec[2].iov_len = 5;
-       }
-       if (xwritev(ToServer, vec, 3) < 0)
-           return false;
-    } else
-       if (xwrite(ToServer, art->data, art->len) < 0)
-           return false;
-    if (GotInterrupt)
-       Interrupted(Article, MessageID);
-    if (Debug) {
-       fprintf(stderr, "> [ article %lu ]\n", (unsigned long) art->len);
-       fprintf(stderr, "> .\n");
-    }
-
-    if (CanStream) return true;        /* streaming mode does not wait for ACK */
-
-    /* What did the remote site say? */
-    if (!REMread(buff, (int)sizeof buff)) {
-        syswarn("no reply after sending %s", Article);
-       return false;
-    }
-    if (GotInterrupt)
-       Interrupted(Article, MessageID);
-    if (Debug)
-       fprintf(stderr, "< %s", buff);
-
-    /* Parse the reply. */
-    switch (atoi(buff)) {
-    default:
-        warn("unknown reply after %s -- %s", Article, buff);
-       if (DoRequeue)
-           Requeue(Article, MessageID);
-       break;
-    case NNTP_BAD_COMMAND_VAL:
-    case NNTP_SYNTAX_VAL:
-    case NNTP_ACCESS_VAL:
-       /* The receiving server is likely confused...no point in continuing */
-        syslog(L_FATAL, GOT_BADCOMMAND, REMhost, MessageID, REMclean(buff));
-        RequeueRestAndExit(Article, MessageID);
-        /* NOTREACHED */
-    case NNTP_RESENDIT_VAL:
-    case NNTP_GOODBYE_VAL:
-       Requeue(Article, MessageID);
-       break;
-    case NNTP_TOOKIT_VAL:
-       STATaccepted++;
-       STATacceptedsize += (double)art->len;
-       break;
-    case NNTP_REJECTIT_VAL:
-        if (logRejects)
-            syslog(L_NOTICE, REJECTED, REMhost,
-                   MessageID, Article, REMclean(buff));
-       STATrejected++;
-       STATrejectedsize += (double)art->len;
-       break;
-    }
-
-    /* Article sent, or we requeued it. */
-    return true;
-}
-\f
-
-/*
-**  Get the Message-ID header from an open article.
-*/
-static char *
-GetMessageID(ARTHANDLE *art) {
-    static char        *buff;
-    static int buffsize = 0;
-    const char *p, *q;
-
-    p = wire_findheader(art->data, art->len, "Message-ID");
-    if (p == NULL)
-       return NULL;
-    for (q = p; q < art->data + art->len; q++) {
-        if (*q == '\r' || *q == '\n')
-            break;
-    }
-    if (q == art->data + art->len)
-       return NULL;
-    if (buffsize < q - p) {
-       if (buffsize == 0)
-           buff = xmalloc(q - p + 1);
-       else
-            buff = xrealloc(buff, q - p + 1);
-       buffsize = q - p;
-    }
-    memcpy(buff, p, q - p);
-    buff[q - p] = '\0';
-    return buff;
-}
-\f
-
-/*
-**  Mark that we got interrupted.
-*/
-static RETSIGTYPE
-CATCHinterrupt(int s) {
-    GotInterrupt = true;
-
-    /* Let two interrupts kill us. */
-    xsignal(s, SIG_DFL);
-}
-
-
-/*
-**  Mark that the alarm went off.
-*/
-static RETSIGTYPE
-CATCHalarm(int s UNUSED)
-{
-    GotAlarm = true;
-    if (JMPyes)
-       longjmp(JMPwhere, 1);
-}
-
-/* check articles in streaming NNTP mode
-** return true on failure.
-*/
-static bool
-check(int i) {
-    char       buff[NNTP_STRLEN];
-
-    /* send "check <ID>" to the other system */
-    snprintf(buff, sizeof(buff), "check %s", stbuf[i].st_id);
-    if (!REMwrite(buff, (int)strlen(buff), false)) {
-        syswarn("cannot check article");
-       return true;
-    }
-    STAToffered++;
-    if (Debug) {
-       if (stbuf[i].st_retry)
-           fprintf(stderr, "> %s (retry %d)\n", buff, stbuf[i].st_retry);
-       else
-           fprintf(stderr, "> %s\n", buff);
-    }
-    if (GotInterrupt)
-       Interrupted(stbuf[i].st_fname, stbuf[i].st_id);
-
-    /* That all.  Response is checked later by strlisten() */
-    return false;
-}
-
-/* Send article in "takethis <id> streaming NNTP mode.
-** return true on failure.
-*/
-static bool
-takethis(int i) {
-    char       buff[NNTP_STRLEN];
-
-    if (!stbuf[i].art) {
-        warn("internal error: null article for %s in takethis",
-             stbuf[i].st_fname);
-        return true;
-    }
-    /* send "takethis <ID>" to the other system */
-    snprintf(buff, sizeof(buff), "takethis %s", stbuf[i].st_id);
-    if (!REMwrite(buff, (int)strlen(buff), false)) {
-        syswarn("cannot send takethis");
-        return true;
-    }
-    if (Debug)
-        fprintf(stderr, "> %s\n", buff);
-    if (GotInterrupt)
-        Interrupted((char *)0, (char *)0);
-    if (!REMsendarticle(stbuf[i].st_fname, stbuf[i].st_id, stbuf[i].art))
-        return true;
-    stbuf[i].st_size = stbuf[i].art->len;
-    article_free(stbuf[i].art); /* should not need file again */
-    stbuf[i].art = 0;          /* so close to free descriptor */
-    stbuf[i].st_age = 0;
-    /* That all.  Response is checked later by strlisten() */
-    return false;
-}
-
-
-/* listen for responses.  Process acknowledgments to remove items from
-** the queue.  Also sends the articles on request.  Returns true on error.
-** return true on failure.
-*/
-static bool
-strlisten(void)
-{
-    int                resp;
-    int                i;
-    char       *id, *p;
-    char       buff[NNTP_STRLEN];
-    int                hash;
-
-    while(true) {
-       if (!REMread(buff, (int)sizeof buff)) {
-            syswarn("no reply to check");
-           return true;
-       }
-       if (GotInterrupt)
-           Interrupted((char *)0, (char *)0);
-       if (Debug)
-           fprintf(stderr, "< %s", buff);
-
-       /* Parse the reply. */
-       resp =  atoi(buff);
-       /* Skip the 1XX informational messages */
-       if ((resp >= 100) && (resp < 200)) continue;
-       switch (resp) { /* first time is to verify it */
-       case NNTP_ERR_GOTID_VAL:
-       case NNTP_OK_SENDID_VAL:
-       case NNTP_OK_RECID_VAL:
-       case NNTP_ERR_FAILID_VAL:
-       case NNTP_RESENDID_VAL:
-           if ((id = strchr(buff, '<')) != NULL) {
-               p = strchr(id, '>');
-               if (p) *(p+1) = '\0';
-               hash = stidhash(id);
-               i = stindex(id, hash);  /* find table entry */
-               if (i < 0) { /* should not happen */
-                   syslog(L_NOTICE, CANT_FINDIT, REMhost, REMclean(buff));
-                   return (true); /* can't find it! */
-               }
-           } else {
-               syslog(L_NOTICE, CANT_PARSEIT, REMhost, REMclean(buff));
-               return (true);
-           }
-           break;
-       case NNTP_GOODBYE_VAL:
-           /* Most likely out of space -- no point in continuing. */
-           syslog(L_NOTICE, IHAVE_FAIL, REMhost, REMclean(buff));
-           return true;
-           /* NOTREACHED */
-       default:
-           syslog(L_NOTICE, UNEXPECTED, REMhost, REMclean(buff));
-           if (Debug)
-               fprintf(stderr, "Unknown reply \"%s\"",
-                                                   buff);
-           return (true);
-       }
-       switch (resp) { /* now we take some action */
-       case NNTP_RESENDID_VAL: /* remote wants it later */
-           /* try again now because time has passed */
-           if (stbuf[i].st_retry < STNRETRY) {
-               if (check(i)) return true;
-               stbuf[i].st_retry++;
-               stbuf[i].st_age = 0;
-           } else { /* requeue to disk for later */
-               Requeue(stbuf[i].st_fname, stbuf[i].st_id);
-               strel(i); /* release entry */
-           }
-           break;
-       case NNTP_ERR_GOTID_VAL:        /* remote doesn't want it */
-           strel(i); /* release entry */
-           STATrefused++;
-           stnofail = 0;
-           break;
-               
-       case NNTP_OK_SENDID_VAL:        /* remote wants article */
-           if (takethis(i)) return true;
-           stnofail++;
-           break;
-
-       case NNTP_OK_RECID_VAL: /* remote received it OK */
-           STATacceptedsize += (double) stbuf[i].st_size;
-           strel(i); /* release entry */
-           STATaccepted++;
-           break;
-               
-       case NNTP_ERR_FAILID_VAL:
-           STATrejectedsize += (double) stbuf[i].st_size;
-           if (logRejects)
-               syslog(L_NOTICE, REJ_STREAM, REMhost,
-                   stbuf[i].st_fname, REMclean(buff));
-/* XXXXX Caution THERE BE DRAGONS, I don't think this logs properly
-   The message ID is returned in the peer response... so this is redundant
-                   stbuf[i].st_id, stbuf[i].st_fname, REMclean(buff)); */
-           strel(i); /* release entry */
-           STATrejected++;
-           stnofail = 0;
-           break;
-       }
-       break;
-    }
-    return (false);
-}
-
-/*
-**  Print a usage message and exit.
-*/
-static void
-Usage(void)
-{
-    die("Usage: innxmit [-acdHlprs] [-t#] [-T#] host file");
-}
-
-
-/*
-**  Open an article.  If the argument is a token, retrieve the article via
-**  the storage API.  Otherwise, open the file and fake up an ARTHANDLE for
-**  it.  Only fill in those fields that we'll need.  Articles not retrieved
-**  via the storage API will have a type of TOKEN_EMPTY.
-*/
-static ARTHANDLE *
-article_open(const char *path, const char *id)
-{
-    TOKEN token;
-    ARTHANDLE *article;
-    int fd, length;
-    struct stat st;
-    char *p;
-
-    if (IsToken(path)) {
-        token = TextToToken(path);
-        article = SMretrieve(token, RETR_ALL);
-        if (article == NULL) {
-            if (SMerrno == SMERR_NOENT || SMerrno == SMERR_UNINIT)
-                STATmissing++;
-            else {
-                warn("requeue %s: %s", path, SMerrorstr);
-                Requeue(path, id);
-            }
-        }
-        return article;
-    } else {
-        char *data;
-        fd = open(path, O_RDONLY);
-        if (fd < 0)
-            return NULL;
-        if (fstat(fd, &st) < 0) {
-            syswarn("requeue %s", path);
-            Requeue(path, id);
-            return NULL;
-        }
-        article = xmalloc(sizeof(ARTHANDLE));
-        article->type = TOKEN_EMPTY;
-        article->len = st.st_size;
-        data = xmalloc(article->len);
-        if (xread(fd, data, article->len) < 0) {
-            syswarn("requeue %s", path);
-            free(data);
-            free(article);
-            close(fd);
-            Requeue(path, id);
-            return NULL;
-        }
-        close(fd);
-        p = memchr(data, '\n', article->len);
-        if (p == NULL || p == data) {
-            warn("requeue %s: cannot find headers", path);
-            free(data);
-            free(article);
-            Requeue(path, id);
-            return NULL;
-        }
-        if (p[-1] != '\r') {
-            p = ToWireFmt(data, article->len, (size_t *)&length);
-            free(data);
-            data = p;
-            article->len = length;
-        }
-        article->data = data;
-        return article;
-    }
-}
-
-
-/*
-**  Free an article, using the type field to determine whether to free it
-**  via the storage API.
-*/
-static void
-article_free(ARTHANDLE *article)
-{
-    if (article->type == TOKEN_EMPTY) {
-        free((char *)article->data);
-        free(article);
-    } else
-        SMfreearticle(article);
-}
-
-
-int main(int ac, char *av[]) {
-    static char                SKIPPING[] = "Skipping \"%s\" --%s?\n";
-    int                        i;
-    char               *p;
-    ARTHANDLE          *art;
-    FILE               *From;
-    FILE               *To;
-    char               buff[8192+128];
-    char               *Article;
-    char               *MessageID;
-    RETSIGTYPE         (*old)(int) = NULL;
-    unsigned int       ConnectTimeout;
-    unsigned int       TotalTimeout;
-    int                 port = NNTP_PORT;
-    bool               val;
-    char                *path;
-
-    openlog("innxmit", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
-    message_program_name = "innxmit";
-
-    /* Set defaults. */
-    if (!innconf_read(NULL))
-        exit(1);
-
-    ConnectTimeout = 0;
-    TotalTimeout = 0;
-    
-    umask(NEWSUMASK);
-
-    /* Parse JCL. */
-    while ((i = getopt(ac, av, "lacdHprst:T:vP:")) != EOF)
-       switch (i) {
-       default:
-           Usage();
-           /* NOTREACHED */
-       case 'P':
-           port = atoi(optarg);
-           break;
-       case 'a':
-           AlwaysRewrite = true;
-           break;
-       case 'c':
-           DoCheck = false;
-           break;
-       case 'd':
-           Debug = true;
-           break;
-       case 'H':
-           HeadersFeed = true;
-           break;
-        case 'l':
-            logRejects = true ;
-            break ;
-       case 'p':
-           AlwaysRewrite = true;
-           Purging = true;
-           break;
-       case 'r':
-           DoRequeue = false;
-           break;
-       case 's':
-           TryStream = false;
-           break;
-       case 't':
-           ConnectTimeout = atoi(optarg);
-           break;
-       case 'T':
-           TotalTimeout = atoi(optarg);
-           break;
-       case 'v':
-           STATprint = true;
-           break;
-       }
-    ac -= optind;
-    av += optind;
-
-    /* Parse arguments; host and filename. */
-    if (ac != 2)
-       Usage();
-    REMhost = av[0];
-    BATCHname = av[1];
-
-    if (chdir(innconf->patharticles) < 0)
-        sysdie("cannot cd to %s", innconf->patharticles);
-
-    val = true;
-    if (!SMsetup(SM_PREOPEN,(void *)&val))
-        die("cannot set up the storage manager");
-    if (!SMinit())
-        die("cannot initialize the storage manager: %s", SMerrorstr);
-
-    /* Open the batch file and lock others out. */
-    if (BATCHname[0] != '/') {
-        BATCHname = concatpath(innconf->pathoutgoing, av[1]);
-    }
-    if (((i = open(BATCHname, O_RDWR)) < 0) || ((BATCHqp = QIOfdopen(i)) == NULL)) {
-        syswarn("cannot open %s", BATCHname);
-       SMshutdown();
-       exit(1);
-    }
-    if (!inn_lock_file(QIOfileno(BATCHqp), INN_LOCK_WRITE, true)) {
-#if    defined(EWOULDBLOCK)
-       if (errno == EWOULDBLOCK) {
-           SMshutdown();
-           exit(0);
-       }
-#endif /* defined(EWOULDBLOCK) */
-        syswarn("cannot lock %s", BATCHname);
-       SMshutdown();
-       exit(1);
-    }
-
-    /* Get a temporary name in the same directory as the batch file. */
-    p = strrchr(BATCHname, '/');
-    *p = '\0';
-    BATCHtemp = concatpath(BATCHname, "bchXXXXXX");
-    *p = '/';
-
-    /* Set up buffer used by REMwrite. */
-    REMbuffer = xmalloc(OUTPUT_BUFFER_SIZE);
-    REMbuffend = &REMbuffer[OUTPUT_BUFFER_SIZE];
-    REMbuffptr = REMbuffer;
-
-    /* Start timing. */
-    STATbegin = TMRnow_double();
-
-    if (!Purging) {
-       /* Open a connection to the remote server. */
-       if (ConnectTimeout) {
-           GotAlarm = false;
-           old = xsignal(SIGALRM, CATCHalarm);
-            if (setjmp(JMPwhere)) {
-                warn("cannot connect to %s: timed out", REMhost);
-                SMshutdown();
-                exit(1);
-            }
-           JMPyes = true;
-           alarm(ConnectTimeout);
-       }
-       if (NNTPconnect(REMhost, port, &From, &To, buff) < 0 || GotAlarm) {
-           i = errno;
-            warn("cannot connect to %s: %s", REMhost,
-                 buff[0] ? REMclean(buff) : strerror(errno));
-           if (GotAlarm)
-               syslog(L_NOTICE, CANT_CONNECT, REMhost, "timeout");
-           else 
-               syslog(L_NOTICE, CANT_CONNECT, REMhost,
-                   buff[0] ? REMclean(buff) : strerror(i));
-           SMshutdown();
-           exit(1);
-       }
-       if (Debug)
-           fprintf(stderr, "< %s\n", REMclean(buff));
-       if (NNTPsendpassword(REMhost, From, To) < 0 || GotAlarm) {
-           i = errno;
-            syswarn("cannot authenticate with %s", REMhost);
-           syslog(L_ERROR, CANT_AUTHENTICATE,
-               REMhost, GotAlarm ? "timeout" : strerror(i));
-           /* Don't send quit; we want the remote to print a message. */
-           SMshutdown();
-           exit(1);
-       }
-       if (ConnectTimeout) {
-           alarm(0);
-           xsignal(SIGALRM, old);
-           JMPyes = false;
-       }
-
-       /* We no longer need standard I/O. */
-       FromServer = fileno(From);
-       ToServer = fileno(To);
-
-       if (TryStream) {
-           if (!REMwrite(modestream, (int)strlen(modestream), false)) {
-                syswarn("cannot negotiate %s", modestream);
-           }
-           if (Debug)
-               fprintf(stderr, ">%s\n", modestream);
-           /* Does he understand mode stream? */
-           if (!REMread(buff, (int)sizeof buff)) {
-                syswarn("no reply to %s", modestream);
-           } else {
-               if (Debug)
-                   fprintf(stderr, "< %s", buff);
-
-               /* Parse the reply. */
-               switch (atoi(buff)) {
-               default:
-                    warn("unknown reply to %s -- %s", modestream, buff);
-                   CanStream = false;
-                   break;
-               case NNTP_OK_STREAM_VAL:        /* YES! */
-                   CanStream = true;
-                   break;
-                case NNTP_AUTH_NEEDED_VAL: /* authentication refusal */
-               case NNTP_BAD_COMMAND_VAL: /* normal refusal */
-                   CanStream = false;
-                   break;
-               }
-           }
-           if (CanStream) {
-               for (i = 0; i < STNBUF; i++) { /* reset buffers */
-                   stbuf[i].st_fname = 0;
-                   stbuf[i].st_id = 0;
-                   stbuf[i].art = 0;
-               }
-               stnq = 0;
-           }
-       }
-       if (HeadersFeed) {
-           if (!REMwrite(modeheadfeed, strlen(modeheadfeed), false))
-                syswarn("cannot negotiate %s", modeheadfeed);
-           if (Debug)
-               fprintf(stderr, ">%s\n", modeheadfeed);
-           if (!REMread(buff, sizeof buff)) {
-                syswarn("no reply to %s", modeheadfeed);
-           } else {
-               if (Debug)
-                   fprintf(stderr, "< %s", buff);
-
-               /* Parse the reply. */
-               switch (atoi(buff)) {
-               case 250:               /* YES! */
-                   break;
-               case NNTP_BAD_COMMAND_VAL: /* normal refusal */
-                    die("%s not allowed -- %s", modeheadfeed, buff);
-               default:
-                    die("unknown reply to %s -- %s", modeheadfeed, buff);
-               }
-           }
-       }
-    }
-
-    /* Set up signal handlers. */
-    xsignal(SIGHUP, CATCHinterrupt);
-    xsignal(SIGINT, CATCHinterrupt);
-    xsignal(SIGTERM, CATCHinterrupt);
-    xsignal(SIGPIPE, SIG_IGN);
-    if (TotalTimeout) {
-       xsignal(SIGALRM, CATCHalarm);
-       alarm(TotalTimeout);
-    }
-
-    path = concatpath(innconf->pathdb, _PATH_HISTORY);
-    History = HISopen(path, innconf->hismethod, HIS_RDONLY);
-    free(path);
-
-    /* Main processing loop. */
-    GotInterrupt = false;
-    GotAlarm = false;
-    for (Article = NULL, MessageID = NULL; ; ) {
-       if (GotAlarm) {
-            warn("timed out");
-           /* Don't resend the current article. */
-           RequeueRestAndExit((char *)NULL, (char *)NULL);
-       }
-       if (GotInterrupt)
-           Interrupted(Article, MessageID);
-
-       if ((Article = QIOread(BATCHqp)) == NULL) {
-           if (QIOtoolong(BATCHqp)) {
-                warn("skipping long line in %s", BATCHname);
-               QIOread(BATCHqp);
-               continue;
-           }
-           if (QIOerror(BATCHqp)) {
-                syswarn("cannot read %s", BATCHname);
-               ExitWithStats(1);
-           }
-
-           /* Normal EOF -- we're done. */
-           QIOclose(BATCHqp);
-           BATCHqp = NULL;
-           break;
-       }
-
-       /* Ignore blank lines. */
-       if (*Article == '\0')
-           continue;
-
-       /* Split the line into possibly two fields. */
-       if (Article[0] == '/'
-        && Article[strlen(innconf->patharticles)] == '/'
-        && strncmp(Article, innconf->patharticles, strlen(innconf->patharticles)) == 0)
-           Article += strlen(innconf->patharticles) + 1;
-       if ((MessageID = strchr(Article, ' ')) != NULL) {
-           *MessageID++ = '\0';
-           if (*MessageID != '<'
-               || (p = strrchr(MessageID, '>')) == NULL
-               || *++p != '\0') {
-                warn("ignoring line %s %s...", Article, MessageID);
-               continue;
-           }
-       }
-
-       if (*Article == '\0') {
-           if (MessageID)
-                warn("empty file name for %s in %s", MessageID, BATCHname);
-           else
-                warn("empty file name, no message ID in %s", BATCHname);
-           /* We could do a history lookup. */
-           continue;
-       }
-
-       if (Purging && MessageID != NULL && !Expired(MessageID)) {
-           Requeue(Article, MessageID);
-           continue;
-       }
-
-        /* Drop articles with a message ID longer than NNTP_MSGID_MAXLEN to
-           avoid overrunning buffers and throwing the server on the
-           receiving end a blow from behind. */
-        if (MessageID != NULL && strlen(MessageID) > NNTP_MSGID_MAXLEN) {
-            warn("dropping article in %s: long message ID %s", BATCHname,
-                 MessageID);
-            continue;
-        }
-
-        art = article_open(Article, MessageID);
-        if (art == NULL)
-           continue;
-
-       if (Purging) {
-            article_free(art);
-           Requeue(Article, MessageID);
-           continue;
-       }
-
-       /* Get the Message-ID from the article if we need to. */
-       if (MessageID == NULL) {
-           if ((MessageID = GetMessageID(art)) == NULL) {
-                warn(SKIPPING, Article, "no message ID");
-                article_free(art);
-               continue;
-           }
-       }
-       if (GotInterrupt)
-           Interrupted(Article, MessageID);
-
-       /* Offer the article. */
-       if (CanStream) {
-           int lim;
-           int hash;
-
-           hash = stidhash(MessageID);
-           if (stindex(MessageID, hash) >= 0) { /* skip duplicates in queue */
-               if (Debug)
-                   fprintf(stderr, "Skipping duplicate ID %s\n",
-                                                           MessageID);
-                article_free(art);
-               continue;
-           }
-           /* This code tries to optimize by sending a burst of "check"
-            * commands before flushing the buffer.  This should result
-            * in several being sent in one packet reducing the network
-            * overhead.
-            */
-           if (DoCheck && (stnofail < STNC)) lim = STNBUF;
-           else                              lim = STNBUFL;
-           if (stnq >= lim) { /* need to empty a buffer */
-               while (stnq >= STNBUFL) { /* or several */
-                   if (strlisten()) {
-                       RequeueRestAndExit(Article, MessageID);
-                   }
-               }
-           }
-           /* save new article in the buffer */
-           i = stalloc(Article, MessageID, art, hash);
-           if (i < 0) {
-                article_free(art);
-               RequeueRestAndExit(Article, MessageID);
-           }
-           if (DoCheck && (stnofail < STNC)) {
-               if (check(i)) {
-                   RequeueRestAndExit((char *)NULL, (char *)NULL);
-               }
-           } else {
-                STAToffered++ ;
-               if (takethis(i)) {
-                   RequeueRestAndExit((char *)NULL, (char *)NULL);
-               }
-           }
-           /* check for need to resend any IDs */
-           for (i = 0; i < STNBUF; i++) {
-               if ((stbuf[i].st_fname) && (stbuf[i].st_fname[0] != '\0')) {
-                   if (stbuf[i].st_age++ > stnq) {
-                       /* This should not happen but just in case ... */
-                       if (stbuf[i].st_retry < STNRETRY) {
-                           if (check(i)) /* resend check */
-                               RequeueRestAndExit((char *)NULL, (char *)NULL);
-                           retries++;
-                           stbuf[i].st_retry++;
-                           stbuf[i].st_age = 0;
-                       } else { /* requeue to disk for later */
-                           Requeue(stbuf[i].st_fname, stbuf[i].st_id);
-                           strel(i); /* release entry */
-                       }
-                   }
-               }
-           }
-           continue; /* next article */
-       }
-       snprintf(buff, sizeof(buff), "ihave %s", MessageID);
-       if (!REMwrite(buff, (int)strlen(buff), false)) {
-            syswarn("cannot offer article");
-            article_free(art);
-           RequeueRestAndExit(Article, MessageID);
-       }
-       STAToffered++;
-       if (Debug)
-           fprintf(stderr, "> %s\n", buff);
-       if (GotInterrupt)
-           Interrupted(Article, MessageID);
-
-       /* Does he want it? */
-       if (!REMread(buff, (int)sizeof buff)) {
-            syswarn("no reply to ihave");
-            article_free(art);
-           RequeueRestAndExit(Article, MessageID);
-       }
-       if (GotInterrupt)
-           Interrupted(Article, MessageID);
-       if (Debug)
-           fprintf(stderr, "< %s", buff);
-
-       /* Parse the reply. */
-       switch (atoi(buff)) {
-       default:
-            warn("unknown reply to %s -- %s", Article, buff);
-           if (DoRequeue)
-               Requeue(Article, MessageID);
-           break;
-        case NNTP_BAD_COMMAND_VAL:
-        case NNTP_SYNTAX_VAL:
-        case NNTP_ACCESS_VAL:
-            /* The receiving server is likely confused...no point in continuing */
-            syslog(L_FATAL, GOT_BADCOMMAND, REMhost, MessageID, REMclean(buff));
-           RequeueRestAndExit(Article, MessageID);
-           /* NOTREACHED */
-        case NNTP_AUTH_NEEDED_VAL:
-       case NNTP_RESENDIT_VAL:
-       case NNTP_GOODBYE_VAL:
-           /* Most likely out of space -- no point in continuing. */
-           syslog(L_NOTICE, IHAVE_FAIL, REMhost, REMclean(buff));
-           RequeueRestAndExit(Article, MessageID);
-           /* NOTREACHED */
-       case NNTP_SENDIT_VAL:
-           if (!REMsendarticle(Article, MessageID, art))
-               RequeueRestAndExit(Article, MessageID);
-           break;
-       case NNTP_HAVEIT_VAL:
-           STATrefused++;
-           break;
-#if    defined(NNTP_SENDIT_LATER)
-       case NNTP_SENDIT_LATER_VAL:
-           Requeue(Article, MessageID);
-           break;
-#endif /* defined(NNTP_SENDIT_LATER) */
-       }
-
-        article_free(art);
-    }
-    if (CanStream) { /* need to wait for rest of ACKs */
-       while (stnq > 0) {
-           if (strlisten()) {
-               RequeueRestAndExit((char *)NULL, (char *)NULL);
-           }
-       }
-    }
-
-    if (BATCHfp != NULL)
-       /* We requeued something, so close the temp file. */
-       CloseAndRename();
-    else if (unlink(BATCHname) < 0 && errno != ENOENT)
-        syswarn("cannot remove %s", BATCHtemp);
-    ExitWithStats(0);
-    /* NOTREACHED */
-    return 0;
-}