src/article.c

/* [<][>]
[^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following functions.
  1. rfc822lower
  2. newsDate
  3. art_hilo
  4. crosspost
  5. emit_follows
  6. find_header_arg
  7. authorise_newsgroups
  8. auth_article
  9. bad_article
  10. good_article
  11. CMDarticle

/* $Id: article.c,v 1.8 2002/03/29 09:47:55 proff Exp $ */

#include "nglobal.h"
#include "mmap.h"

#include "acc.h"
#include "filter.h"
#include "group.h"
#include "history.h"
#include "lock.h"
#include "nlist.h"
#include "xover.h"

#include "article.h"


EXPORT void rfc822lower (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
        for (; *s != '@'; s++)
                if (!*s)
                        return;
        for (s++; *s; s++)
                *s = tolower (*s);
}

EXPORT char *newsDate (time_t t)
/* [<][>][^][v][top][bottom][index][help] */
{
        static char buf[80];
        struct tm *tm = localtime (&t);

        strftime (buf, sizeof buf, "%d %b %Y %H:%M:%S %Z", tm);
        return buf;
}


static void art_hilo(char *path)
/* [<][>][^][v][top][bottom][index][help] */
{
    int artno;
    struct newsgroup *n;
    char *p = strrchr (path, '/');
    if (!p)
        return;
    *p = '\0';
    strExchange(path, '/', '.');
    n = newsgroupFindAddLock(path, NULL, TRUE);
    if (!n)
        logd (("art_hilo(): no such group '%s'", path));
    strExchange(path, '.', '/');
    *p = '/';
    if (!n)
        return;
    artno = strToi (p+1);
    if (!n->lo || n->lo > artno)
        n->lo = artno;
    if (n->hi < artno)
        n->hi = artno;
    newsgroupUnlockWrite(n);
}


/*
 * we must be in the top level of the servers dir
 * path should be relative to the above
 */

static bool crosspost (struct strStack *stack, char *path, enum cmds type, bool getbyid)
/* [<][>][^][v][top][bottom][index][help] */
{
        char msgid[MAX_MSGID] = "";
        struct strList *v, *links = NULL;
        char xpathbuf[MAX_HEADER];
        char *xpath = NULL;
        char *xref = NULL;
        char fn[MAX_GROUP + 20];
        char *artfile;
        char *s;
        int fd;

        for (s = stack->data; *s; s++)
        {
                if (*s == '\r' || *s == '\n')
                        break;
                if (strnCaseEq (s, "Message-ID:", 11))
                        strSnip(s, MAX_MSGID + 11, "<", ">\r\n", msgid, sizeof msgid);
                else if (strnCaseEq (s, "Xref:", 5))
                        xref = s;
                s = strchr (s, '\n');
                if (!s)         /* badly formed art */
                {
                        logw (("badly formed article head %s", path));
                        return FALSE;
                }
        }
        if (!*msgid)
        {
                logw (("no Message-ID in article '%s' - not cached", path));
                return FALSE;
        }
        if (!xref && getbyid)
        {
                log (("no Xref header or article number, trying xpath <%s>", msgid));
                Cemitf ("xpath <%s>\r\n", msgid);
                Cflush ();
                if (Cget (xpathbuf, sizeof xpathbuf) &&
                    strToi (xpathbuf) == NNTP_NOTHING_FOLLOWS_VAL)
                {
                        xpath = xpathbuf;
                        strStripEOL (xpath);
                        CurrentScfg->share->xpath_good++;
                } else
                    CurrentScfg->share->xpath_fail++;
        }
        if (!getbyid)
        {
                if (safePath(path))
                {
                        if (type == c_head && !strstr (path, "_head"))
                                strcat (path, "_head");
                        links = strListAdd (links, path);
                } else
                        logw (("suspect path: '%.256s'", path));
                
        }
        if (xref)
        {
                char *xrefbuf = Sstrdup (xref);
                char *tok;
                strStripEOL (xrefbuf);
                tok = strtok (xrefbuf, " \t"); /* Xref: */
                if (tok)
                        tok = strtok (NULL, " \t"); /* Xref: foomatic.foo.com */
                if (!tok || !(tok = strtok (NULL, " \t"))) /* xref: foomatic.foo.com alt.fan.proff:1649 */
                {
                        logw (("bad Xref %.*s", MAX_SYSLOG-20, xref));
                } else
                {
                        do
                        {
                                char *p = strchr (tok, ':'); /* alt.fan.proff:1649 */
                                if (!p || (p && strToi (p + 1) <= 0))
                                {
                                        logw (("bad Xref %.*s", MAX_SYSLOG-20, xref));
                                        continue;
                                }
                                /* does another server normally handly this
                                 * group ?
                                 */
                                *p = '\0';
                                if (getServerGroup(tok) != CurrentScfg)
                                        continue;
                                *p = '/'; /* alt.fan.proff/1649 */
                                if (strlen (tok) > (size_t)MAX_GROUP)
                                {
                                        logw (("xref too long %.*s", MAX_SYSLOG-20, xref));
                                        continue;
                                }
                                strExchange (tok, '.', '/'); /* alt/fan/proff/1649 */
                                if (safePath(tok))
                                {
                                        if (type == c_head)
                                        {
                                                sprintf (fn, "%.255s_head", tok);
                                                links = strListAdd (links, fn);
                                        } else
                                                links = strListAdd (links, tok);
                                } else
                                        logw (("suspect path: '%.256s'", tok));

                        } while ((tok = strtok (NULL, " \t")));
                }
                free (xrefbuf);
        }
        if (xpath)              /* !xref */
        {
                char *tok = strtok (xpath, " \t");
                if (!tok || !(tok = strtok (NULL, " \t")))
                        logw (("bad Xref %.*s", MAX_SYSLOG-20, xref));
                else
                        do
                        {
                                if (safePath(tok))
                                {
                                        if (type == c_head)
                                        {
                                                sprintf (fn, "%.255s_head", tok);
                                                links = strListAdd (links, fn);
                                        } else
                                                links = strListAdd (links, tok);
                                } else
                                        logw (("suspect path: '%.256s'", tok));
                        } while ((tok = strtok (NULL, " \t")));
        }
        if (!links)
        {
                logw (("couldn't work out an article number for <%s>", msgid));
                return FALSE;
        }
        artfile = links->head->data;
        fd = open (artfile, O_WRONLY | O_EXCL | O_CREAT, 0000 /* tag for incompleteness */ );
        if (fd == -1)
        {
                if (errno == EEXIST)
                {
                        logw (("unlinking pre-existing article %s", artfile));
                        unlink (artfile);
                        Stats->article_unlinked++;
                } else
                        blddir (artfile);
                fd = open (artfile, O_WRONLY | O_EXCL | O_CREAT, 0000 );
                if (fd<0)
                {
                        loge (("couldn't create article %s", artfile));
                        strListFree (links);
                        return FALSE;
                }
        }
        if (write (fd, stack->data, stack->used - 1) != stack->used - 1)
        {
                loge (("error writing article %.127s", artfile));
                unlink (artfile);
                close (fd);
                strListFree (links);
                return FALSE;
        }
        fchmod(fd, 0664 /* complete */);
        close (fd);
        art_hilo(artfile);

        ;{
                char buf[MAX_HOST+MAX_GROUP+32];
                char *p = strstr (artfile, "_head");
                if (p)
                        *p = '\0';
                sprintf(buf, "%.127s/%.287s", CurrentDir, artfile);
                if (safePath(buf))
                {
                        bool ok;
                        rfc822lower (msgid);
                        ok = hisAdd (msgid, buf);
                        logd (("adding <%s>:%s to %s (%s)", msgid, buf, con->historyFile, ok ? "suceeded" : "failed"));
                } else
                        logw (("suspect path: '%.256s'", buf));
                if (p)
                        *p = '_';
        }
        for (v = links->head->next; v; v = v->next)
        {
                bool linkok;
                char *f = v->data;
                if (strEq (f, artfile))
                        continue;
                if (link (artfile, f) == -1)
                {
                        if (errno != EEXIST)
                                blddir (f);
                        else
                        {
                                logw (("unlinking pre-existing article link %s", f));
                                unlink (f);
                                Stats->article_unlinked++;
                        }
                        if (link (artfile, f) == -1)
                        {
                                loge (("couldn't link %s -> %s", artfile, f));
                                linkok = FALSE;
                        } else
                                linkok = TRUE;

                } else
                        linkok = TRUE;
                if (!linkok)
                    continue;
                logd (("linked %s -> %s", artfile, f));
                Stats->crossposts++;
                Stats->crosspostsBytes += stack->used-1;
                art_hilo(f);
        }
        strListFree (links);
        return TRUE;
}

static int emit_follows (enum cmds type, int artno, char *msgid, bool getbyid)
/* [<][>][^][v][top][bottom][index][help] */
{
        char *m=NULL, *c=NULL; /* stop warning */
        switch (type)
        {
        case c_article:
                m=NNTP_ARTICLE_FOLLOWS; c="article";
                CurrentGroupArtRead++;
                ArtRead++;
                break;
        case c_head:
                m=NNTP_HEAD_FOLLOWS; c="head";
                CurrentGroupArtRead++;
                ArtRead++;
                break;
        case c_body:
                m=NNTP_BODY_FOLLOWS; c="body";
                CurrentGroupArtRead++;
                ArtRead++;
                break;
        case c_stat:
                m=NNTP_NOTHING_FOLLOWS; c="status";
                break;
        default:
                loge(("strange flow"));
                retire_vm_proc(1);
        }
        if (getbyid) /* dumb, but that is how inn does it */
                return emitf("%s %d %s <%s>\r\n", m, artno, c, msgid);
        CurrentGroupArtNum = artno;
        return emitf("%s %d <%s> %s\r\n", m, artno, msgid, c);
}

EXPORT char *find_header_arg(char *s, char *hdr, int len)
/* [<][>][^][v][top][bottom][index][help] */
{
        char *s_orig=s;
        char *p;
        for (;;s=p+1)
        {
                p=len? strnCaseStr(s, hdr, len): strCaseStr(s, hdr);
                if (!p)
                        return NULL;
                if (p==s_orig)
                        goto found;
                if (p[-1]=='\n')
                        goto found;
        }
found:
        p+=strlen(hdr);
        while (isspace(*p))
                p++;
        return p;
}

static int authorise_newsgroups (char *s)
/* [<][>][^][v][top][bottom][index][help] */
{
        char buf[MAX_HEADER];
        char *p = strchr (s, '\r');
        char *p2 = strchr (s, '\n');
        if (!p2)
        {
                logw (("badly formed newsgroups line"));
                return FALSE;
        }
        if (p)
                p = (p<p2)? p: p2;
        else
                p = p2;
        strncpy(buf, s, p-s);
        buf[p-s] = '\0';
        for (s=strtok(buf, ","); s; s=strtok(NULL, ","))
        {
                struct authent *auth;
                strStripLeftRight(s);
                if (!*s)
                        continue;
                auth = authorise(RemoteHosts, s);
                if (auth && auth->read && (!auth->auth || AuthState == valid))
                        return TRUE;
        }
        return FALSE;
}

static bool auth_article (char *art, int hlen, char *msgid)
/* [<][>][^][v][top][bottom][index][help] */
{
        char *p=find_header_arg(art, "Newsgroups:", hlen);
        if (!p || !authorise_newsgroups(p))
        {
                if (!p)
                {
                        logw (("missing newsgroups in <%s> - skipped", msgid));
                        emitrn (NNTP_BOGUSARTICLE);
                } else
                {
                        log (("%s not permitted read access to <%s>", ClientHost, msgid));
                        emitrn (NNTP_PERM);
                }
                return FALSE;
        }
        return TRUE;
}


/* This routine just shoves out a dummy art with the stats in it */

static void bad_article(struct server_cfg *scfg, enum cmds type)
/* [<][>][^][v][top][bottom][index][help] */
{
    switch (type)
        {
        case c_article:
            scfg->share->article_fail++;
            break;
        case c_head:
            scfg->share->head_fail++;
            break;
        case c_body:
            scfg->share->body_fail++;
            break;
        case c_stat:
            scfg->share->stat_fail++;
            break;
        default:
            assert("PC" == "never here");
            break;
        }
}

static void good_article(struct server_cfg *scfg, enum cmds type)
/* [<][>][^][v][top][bottom][index][help] */
{
    switch (type)
        {
        case c_article:
            scfg->share->article_good++;
            break;
        case c_head:
            scfg->share->head_good++;
            break;
        case c_body:
            scfg->share->body_good++;
            break;
        case c_stat:
            scfg->share->stat_good++;
            break;
        default:
            assert("PC" == "never here");
            break;
        }
}

/* handle article, head, body and stat commands.  this routine is
 * insane. most of the insanity is attempting to handle all the
 * differrent msgid, caching, security, filtering,
 * body/stat/article/head variations that can occur without simply
 * waiting until we have a complete article in memory before speaking
 * to the client i.e we try to minimise cache latency as much as is
 * possible. This function has "evolved" over time, and needs a
 * complete re-write -- however it works, and it's fast,
 * so one's game to touch it) */

EXPORT bool CMDarticle (struct command *cmd, char *args, bool dontcache)
/* [<][>][^][v][top][bottom][index][help] */
{
        char bfr[MAX_LINE];
        char artfile[MAX_PATH]="";
        int artno = 0;
        int response;
        int fd = -1;
        char msgid[MAX_MSGID + 20] = "";
        enum cmds type=cmd->val;
        int bytes = 0;
        int cc;
        bool real_head_file = FALSE;
        struct strStack *art_stack = NULL;
        bool getbyid;
        bool f_filt = con->contentFilters && CurrentGroupAuth && CurrentGroupAuth->filter_chain;

        strStripLeftRight (args);
        if (strSnip(args, 0, "<", ">\r\n", msgid, sizeof msgid)<1 &&
            sscanf (args, "%*s %d", &artno) != 1)
        {
                artno = CurrentGroupArtNum;
                sprintf(args, "%.32s %d", cmd->cmd, artno);
        }
        if (type == c_stat)
                strncpy (args, "head", 4);
        getbyid = *msgid? TRUE: FALSE;
        if (getbyid)
                CS->by_msgid++;
        else
                CS->by_artnum++;
        if (!getbyid && (!*CurrentGroup || !setGD()))
        {
                emitrn (NNTP_NOTINGROUP);
                return FALSE;
        }
        if (getbyid)
        {
                if (CurrentIDScfg)
                        CurrentScfg = CurrentIDScfg;
                
        } else
                CurrentScfg = CurrentGroupScfg;
        /*
         * we can't immediately tell if the article belongs to a cached
         * server or not if we are doing a getbyid()
         */
        if (getbyid || CurrentScfg->article_timeout) 
        {
                if (getbyid) /* find <msgid> -> server/group/article in history file */
                {
                        char *p;
                        rfc822lower (msgid);
                        /*
                         * heads are stored as group/artnum_head
                         */
                        p = hisGet (msgid);
                        if (p)
                        {
                                char *p2;
                                p2 = strchr(p, '/'); /* isolate server */
                                if (p2)
                                {
                                        struct server_cfg *scfg;
                                        *p2 = '\0';
                                        scfg = findScfg (p); /* look it up */
                                        if (scfg)
                                        {
                                                CurrentScfg = scfg;
                                                if (scfg->article_timeout) /* this server caches */
                                                {
                                                        char *p3 = strrchr(p2+1, '/'); /* extract the article number */
                                                        if (p3)
                                                        {
                                                                artno = strToi(p3+1);
                                                                if (artno>0)
                                                                {
                                                                        *p2 = '/'; /* put the / back */
                                                                        if (type == c_head || type == c_stat)
                                                                        {
                                                                                sprintf(artfile, "%.127s/%.384s_head", con->cacheDir, p);
                                                                                real_head_file = TRUE;
                                                                        } else
                                                                                sprintf(artfile, "%.127s/%.384s", con->cacheDir, p);
                                                                }
                                                        }
                                                }
                                        }
                                }
                        }
                } else /* ahh, the simple alternative (!getbyid) */
                {
                        if (type == c_head || type == c_stat)
                        {
                                sprintf (artfile, "%d_head", artno);
                                real_head_file = TRUE;
                        } else
                                sprintf (artfile, "%d", artno);
                }
                if (*artfile) /* we have somewhere to look */
                {
                        if ((fd = open (artfile, O_RDONLY)) < 0)
                        {
                                if (type == c_head || type == c_stat)
                                {
                                        /* no _head, try for article */
                                        char *p;
                                        assert ((p = strrchr (artfile, '_')));
                                        *p = '\0';
                                        real_head_file = FALSE;
                                        fd = open (artfile, O_RDONLY);
                                } else  /* c_article or c_body */
                                {
                                        /* no article, check for the head */
                                        strcat (artfile, "_head");
                                        fd = open (artfile, O_RDONLY);
                                        if (fd >= 0)
                                        {
                                                struct stat st;
                                                if (fstat (fd, &st) == 0)
                                                {
                                                        int len = st.st_size;
                                                        char *buf = (char *)mmap (0, len, PROT_READ, MAP_PRIVATE, fd, 0); /* slurrrup */
                                                        if (buf!=(char *)-1)
                                                        {
                                                                int len = st.st_size;
                                                                real_head_file = TRUE;
                                                                art_stack = strnStackAdd (NULL, buf, len);
                                                                if (munmap(buf, len)!=0)
                                                                        loge (("couldn't munmap %s (%d length)", artfile, len));
                                                                logd (("fetched %s (%d bytes) from cache", artfile, len));
                                                        }
                                                } else
                                                close (fd);
                                                fd = -1;
                                        } /* revert, open _head failed */
                                        *strrchr (artfile, '_') = '\0';
                                }
                        }
                }
                if (fd<0)
                {
                        /* replace 'body' with 'article' unless we pulled in a _head */
                        if (type == c_body && !real_head_file)
                        {

                                if (*msgid)
                                        sprintf (args, "article <%.*s>", MAX_MSGID, msgid);
                                else
                                        sprintf (args, "article %d", artno);
                        } else
                                /* replace 'article' with 'body' if we pulled in a _head */
                        if ((type == c_article || type == c_body) && real_head_file)
                        {
                                if (*msgid)
                                        sprintf (args, "body <%.*s>", MAX_MSGID, msgid);
                                else
                                        sprintf (args, "body %d", artno);
                        }
                        if (Cemitrn (args) <= 0 || Cflush () != 0) {
                                logd (("couldn't send command to host (down?)"));
                                if (art_stack)
                                        strStackFree (art_stack);
                                emitrn (NNTP_SERVERTEMPDOWN);
                                return FALSE;
                        }
                }
        } else /* not cached */
        {
                if (Cemitrn (args) <= 0 || Cflush () != 0) {
                        logd (("couldn't send command to host (down?)"));
                        if (art_stack)
                                strStackFree (art_stack);
                        emitrn (NNTP_SERVERTEMPDOWN);
                        return FALSE;
                }
        }
        if (fd<0)               /* cache off or request not in cache */
        {
                bool body;
                int head_len;
                if ((!Cget (bfr, sizeof bfr) ||
                     !(response=strToi (bfr))) || 
                    (response != NNTP_ARTICLE_FOLLOWS_VAL &&
                     response != NNTP_HEAD_FOLLOWS_VAL &&
                     response != NNTP_BODY_FOLLOWS_VAL))
                {
                        int n;
                        struct server_cfg *cf;
                        if (!*msgid)
                        {
                                emit (bfr);
                        bad_art:
                                bad_article(CurrentScfg, type);
                                if (art_stack)
                                        strStackFree (art_stack);
                                return FALSE;
                        }
                        for (n = 0, cf = ServerList; cf && n<con->maxMsgIDsearch; cf = cf->next)
                            {
                                if (CurrentScfg==cf)
                                    continue;
                                logd (("trying article <%s> on server %s", msgid, cf->host));
                                n++;
                                if (!attachServer (cf))
                                    continue;
                                Cfemitrn (cf, args);
                                Cfflush (cf);
                                if (!Cfget (cf, bfr, sizeof (bfr)))
                                    continue;
                                switch (strToi (bfr))
                                    {
                                    case NNTP_ARTICLE_FOLLOWS_VAL:
                                    case NNTP_HEAD_FOLLOWS_VAL:
                                    case NNTP_BODY_FOLLOWS_VAL:
                                        sscanf(bfr, "%*d %d", &artno);
                                        cf->artno = artno;
                                        CurrentIDScfg = CurrentScfg = cf;
                                        goto found;
                                    }
                            }
                        emitrn (NNTP_DONTHAVEIT);
                        goto bad_art;
                found:
                        ;
                } else
                {
                        if (*msgid)
                                sscanf(bfr, "%*d %d", &artno);
                        else
                                strSnip(bfr, 0, "<", ">\r\n", msgid, sizeof msgid);
                        CurrentScfg->artno=artno;
                }
                /*
                 * we only do body's when we are asked for an article or body
                 * and have only a cached _head or caching is off
                 */
                body = strnCaseEq (args, "body", 4);    /* XXX */
                if (!body)
                {
                        /* get the head */
                        /* TODO: Cget this directly into art_stack and
                                 thus avoid copying */
                        for (; (cc = Cget (bfr, sizeof bfr)) && !EL (bfr) && *bfr != '\r' && *bfr !='\n'; bytes += cc)
                        {
                                if (strnCaseEq(bfr, "Xref:", 5))
                                {
                                        char xref[sizeof bfr];
                                        cc = xrefRewriteCopy(CurrentScfg, bfr, xref, TRUE);
                                                if (strchr(xref+9,':')) { /* are there any articles? */
                                                xref[cc++] = '\r';
                                                xref[cc++] = '\n';
                                                art_stack = strnStackAdd (art_stack, xref, cc);
                                        }
                                } else
                                        art_stack = strnStackAdd (art_stack, bfr, cc);
                        }
                        if (!art_stack || cc<1)
                        {
                                if (art_stack)
                                        strStackFree (art_stack);
                                emitrn (NNTP_BOGUSARTICLE);
                                bad_article(CurrentScfg, type);
                                return FALSE;
                        }
                        if (getbyid)
                        {
                                if (con->groupSecurity && !auth_article(art_stack->data, art_stack->used-1, msgid)) /* can't have already group auth'ed <msgid> */
                                {
                                        if (type == c_article || type == c_body) /* drain */
                                                while (Cget(bfr, sizeof bfr) && !EL (bfr));
                                        strStackFree (art_stack);
                                        CS->auth_blocked++;
                                        bad_article(CurrentScfg, type);
                                        return FALSE;
                                }
                        }
                        
                        /* if we pulled it from the cache, then this has already been added */
                        art_stack = strnStackAdd (art_stack, XCACHE, sizeof(XCACHE)-1);
                }
                head_len = art_stack? art_stack->used-1 : 0;
                if (type == c_article || (type == c_body && CurrentScfg->article_timeout))
                        art_stack = strnStackAdd (art_stack, "\r\n", 2);
                if (!f_filt)
                {
                        emit_follows(type, artno, msgid, getbyid); /* 221 52 <839359140.253541@suburbia.net> head etc */
                        if (type==c_head || type == c_article)
                        {
                                emit(art_stack->data);  /* emit the head */
                        }
                }
                if (type == c_body || type == c_article)
                {
                        for (; (cc = Cget (bfr, sizeof bfr)) && !EL (bfr); bytes += cc) /* suck in rest */
                        {
                                if (!f_filt)
                                        emit (bfr);
                                art_stack = strnStackAdd (art_stack, bfr, cc);
                        }
                        if (!art_stack || !cc)
                        {
                                if (art_stack)
                                        strStackFree (art_stack);
                                emitrn (f_filt? NNTP_BOGUSARTICLE: ".");
                                bad_article(CurrentScfg, type);
                                return FALSE;
                        }
                }
                if (f_filt)
                {
                        /* we now have the whole shoomse and can attempt to truffle trough it
                         */
                        char *blocker;
                        if (type == c_body && !CurrentScfg->article_timeout) /* non cached filtered body has no head */
                                blocker = filterText(type,
                                                     CurrentGroupAuth,
                                                     NULL,
                                                     0,
                                                     NULL,
                                                     0,
                                                     art_stack->data, art_stack->used-1);
                        else
                                blocker = filterText(type,
                                                     CurrentGroupAuth,
                                                     art_stack->data,
                                                     head_len,
                                                     art_stack->data,
                                                     art_stack->used-1,
                                                     (type == c_body || type == c_article)? art_stack->data+head_len+2: NULL,
                                                     (type == c_body || type == c_article)? art_stack->used-1-head_len-2: 0);
                        if (blocker)
                        {
                                logd (("content filter %s blocked %s of %s/%d", blocker, cmd->cmd, CurrentDir, artno));
                                emitrn (NNTP_PERM);
                                CS->filter_blocked++;
                                return FALSE;
                        }
                        emit_follows(type, artno, msgid, getbyid);
                        if (type != c_stat)
                        {
                                emit ((type == c_body && CurrentScfg->article_timeout)? art_stack->data+head_len+2: art_stack->data);
                        }
                }
                if (type != c_stat)
                        emitrn (".");
                if (CurrentScfg->article_timeout && !dontcache)
                {
                        if (setGroupDir(NULL, CurrentScfg))
                        {
                                char buf[MAX_PATH];
                                if (!getbyid)
                                {
                                        sprintf(buf, "%.255s/%.32s", CurrentGroup, artfile);
                                        strExchange(buf, '.', '/');
                                }
                                crosspost (art_stack, getbyid? artfile: buf, (type==c_stat)? c_head: type, getbyid);
                        }
                }
                good_article(CurrentScfg, type);
                strStackFree (art_stack);
                return TRUE;
        } else  /* pull it out of the cache. easy */
        {
                struct stat st;
                char *buf = NULL;
                char *body = NULL;      /* stop -Wall complaining */
                char *msgid_header;
                int len = 0; /* stop -Wall complaining */
                int wsize;
                int t;
                if (fstat (fd, &st) != 0 || st.st_size <2)
                        goto err;
                len = st.st_size;
                buf = (char *)mmap (0, len, PROT_READ, MAP_PRIVATE, fd, 0);
                if (buf == (char *)-1)
                        goto err;
                close (fd);
                if (buf[len - 1] != '\n') /* truncated file */
                        goto err;
                if (!real_head_file)
                {
                        body = strstr (buf, "\r\n\r\n");
                        if (!body)
                                goto err;
                        body+=4;
                }
                if (!*msgid)
                {
                        msgid_header = find_header_arg(buf, "Message-ID:", body? body-buf: len);
                        if (!msgid_header) /* shouldn't be in the body! */
                                goto err;
                        /* some sscanf implimentations are REALLY DUMB (e.g OSF/1) - and insist on scanning to the end of 
                         * the string even after the terminators have been reached. We use strSnip instead */
                        if (strSnip(msgid_header, MIN (body? body-buf: len, MAX_MSGID + sizeof("Message-ID:")), "<", ">\r\n", msgid, sizeof msgid)<1)
                            {
                                loge (("strSnip failed"));
                                goto err;
                            }   
                }
                if (getbyid && con->groupSecurity && !auth_article(buf, body? body-buf: len, msgid)) /* can't have already group auth'ed <msgid> */
                {
                    if (munmap(buf, len)!=0)
                        loge (("couldn't munmap %s (%d length)", artfile, len));
                        CS->auth_blocked++;
                        return FALSE;
                }
                if (f_filt)
                {
                         /* head's and stat's use head and ahead filters, while 
                          * article's and body's are use article and ahead filters.
                          */
                        char *blocker = NULL;
                        switch (type)
                        {
                        case c_article:
                        case c_body:
                                blocker = filterText(type, CurrentGroupAuth, buf, body-buf-2, buf, len, body, buf+len-body);
                                break;
                        case c_head:
                        case c_stat:
                                blocker = filterText(type, CurrentGroupAuth, buf, body? body-buf-2: len, NULL, 0, NULL, 0);
                                break;
                        default:
                                break;
                        }
                        if (blocker)
                        {
                                logd (("content filter %s blocked %s of %s/%d", blocker, cmd->cmd, CurrentDir, artno));
                                emitrn (NNTP_PERM);
                                CS->filter_blocked++;
                                if (munmap(buf, len)!=0)
                                        loge (("couldn't munmap %s (%d length)", artfile, len));
                                return FALSE;
                        }
                }
                /* system calls and context switches have a fair degree of over-head.
                 * we attempt to minimise both by using different strategies depending
                 * on the amount of data we are dealing with. if the total amount of
                 * data is small, then we use stdio to buffer it up, so we can flush()
                 * it at the end with only one write(). If we are dealing with a longer
                 * pieces of information (i.e ~>1k), then we write() it directly,
                 * reducing the amount of memory movement, at the expense of two
                 * extra (tiny) writes().
                 *
                 * TODO: save the .\r\n on the end of each head/article during the caching?
                 */
                wsize = emit_follows(type, artno, msgid, getbyid);
                t = 0;
                switch (type)
                {

                case c_article:
                        fflush(clientout); /* keep fd and fh in-sync */
                        /* avoid buffering, push it out in one hit */
                        t = writeClient (buf, len);
                        break;
                case c_head:
                        /* head's tend to be small */
                        fwriteClient (buf, (t = (body? body-2-buf: len)));
                        break;
                case c_body:
                        /* bodies can be large or small */
                        if (buf+len-body > 1024)
                        {
                                fflush(clientout); /* keep fd and fh in-sync */
                                /* avoid buffering, push it out in one hit */
                                writeClient (body, (t = (buf+len-body)));
                        } else
                                fwriteClient (body, (t = (buf+len-body)));
                        break;
                case c_stat:
                        break;
                default:
                        break;
                }
                wsize+=t;
                if (munmap(buf, len)!=0)
                        loge (("couldn't munmap %s (%d length)", artfile, len));
                if (type != c_stat)
                        emitrn (".");
                return TRUE;
              err:
                logw (("bad article '%s' ... unlinked", artfile));
                if (buf && munmap(buf, len)!=0)
                        loge (("couldn't munmap %s (%d length)", artfile, len));
                emitrn (NNTP_DONTHAVEIT); /* we should probably just call ourselves again */
                unlink (artfile);
                return FALSE;
        }
        NOTREACHED;
}

/* [<][>][^][v][top][bottom][index][help] */