
/*
 * FEED.C
 *
 *	Feed-related NNTP commands.
 *
 * (c)Copyright 1998, Matthew Dillon, All Rights Reserved.  Refer to
 *    the COPYRIGHT file in the base directory of this distribution
 *    for specific rights granted.
 */

#include "defs.h"

Prototype void NNTPMode(Connection *conn, char **pptr);
Prototype void NNTPIHave(Connection *conn, char **pptr);
Prototype void NNTPCheck(Connection *conn, char **pptr);
Prototype void NNTPTakeThis(Connection *conn, char **pptr);
Prototype void NNTPPost(Connection *conn, char **pptr);
Prototype int ValidXRef(const char *buf, int len);

void NNAcceptArticleStart(Connection *conn, const char *msgid);
void NNAcceptArticle(Connection *conn);

/*
 * mode stream
 * mode reader
 * mode headfeed
 */

void 
NNTPMode(Connection *conn, char **pptr)
{
    char *mode = parseword(pptr, " \t");
    int ok = 0;

    if (mode) {
	if (strcasecmp(mode, "stream") == 0) {
	    conn->co_Flags |= COF_STREAM;
	    MBPrintf(&conn->co_TMBuf, "203 StreamOK.\r\n");
	    ok = 1;
	}
	if (strcasecmp(mode, "headfeed") == 0) {
	    if (conn->co_Flags & COF_SERVER) {
		conn->co_Flags |= COF_HEADFEED;
		MBPrintf(&conn->co_TMBuf, "250 Mode Command OK.\r\n");
	    } else {
		MBPrintf(&conn->co_TMBuf, "500 Mode Command Failed, no server access.\r\n");
	    }
	    ok = 1;
	}
	if (strcasecmp(mode, "reader") == 0) {
	    if (conn->co_Flags & COF_SERVER) {
		/*
		 * Once out of server mode, you cant get back in and
		 * the reader-mode flags apply.
		 */
		conn->co_Flags &= ~COF_SERVER;
	    }
	    NNWriteHello(conn);
	    return;
	}
    }
    if (ok == 0)
	MBPrintf(&conn->co_TMBuf, "500 Syntax error or bad command\r\n");
    NNCommand(conn);
}

/*
 *
 */

void 
NNTPIHave(Connection *conn, char **pptr)
{
    if (conn->co_Flags & COF_SERVER) {
	const char *msgid = MsgId(parseword(pptr, ""));

	conn->co_Flags &= ~(COF_IHAVE|COF_POST);
	conn->co_Flags |= COF_IHAVE;
	MBPrintf(&conn->co_TMBuf, "335\r\n");
	NNAcceptArticleStart(conn, msgid);
    } else {
	MBPrintf(&conn->co_TMBuf, "480 Transfer permission denied\r\n");
	NNCommand(conn);
    }
}

void 
NNTPCheck(Connection *conn, char **pptr)
{
    const char *msgid = MsgId(parseword(pptr, " \t"));

    if (conn->co_Flags & COF_SERVER) {
	if (strcmp(msgid, "<>") == 0)
	    MBPrintf(&conn->co_TMBuf, "438 %s\r\n", msgid);
	else
	    MBPrintf(&conn->co_TMBuf, "238 %s\r\n", msgid);
    } else {
	MBPrintf(&conn->co_TMBuf, "500 What?\r\n");
    }
    NNCommand(conn);
}

void 
NNTPTakeThis(Connection *conn, char **pptr)
{
    const char *msgid = MsgId(parseword(pptr, ""));

    conn->co_Flags &= ~(COF_IHAVE|COF_POST);

    if (conn->co_Flags & COF_SERVER) {
	NNAcceptArticleStart(conn, msgid);
    } else {
	MBPrintf(&conn->co_TMBuf, "500 What?\r\n");
	NNCommand(conn);
    }
}

void
NNTPPost(Connection *conn, char **pptr)
{
    if (conn->co_Auth.dr_Flags & DF_POST) {
	conn->co_Flags &= ~(COF_IHAVE|COF_POST);
	conn->co_Flags |= COF_POST;
	MBPrintf(&conn->co_TMBuf, "340 Ok\r\n");
	NNAcceptArticleStart(conn, NULL);
    } else {
	MBPrintf(&conn->co_TMBuf, "440 Posting Not Allowed.\r\n");
	NNCommand(conn);
    }
}

void
NNAcceptArticleStart(Connection *conn, const char *msgid)
{
    zfreeStr(&conn->co_MemPool, &conn->co_IHaveMsgId);
    if (msgid)
	conn->co_IHaveMsgId = zallocStr(&conn->co_MemPool, msgid);
    conn->co_Flags |= COF_INHEADER;
    conn->co_Flags &= ~COF_WASCONTROL;
    conn->co_ByteCounter = 0;
    conn->co_BytesHeader = 0;
    MBFree(&conn->co_ArtBuf);
    NNAcceptArticle(conn);
}

void
NNAcceptArticle(Connection *conn)
{
    char *buf;
    int len;
    int maxl = 1000;

    conn->co_Func = NNAcceptArticle;
    conn->co_State = "accart";

    while ((len = MBReadLine(&conn->co_RMBuf, &buf)) > 0 && --maxl > 0) {
	/*
	 * expected EOF ('.' on a line by itself)
	 */
	if (len == 3 && strcmp(buf, ".\r") == 0) {
	    if (conn->co_Flags & COF_HEADFEED)	/* can't use bytecounter */
		conn->co_ByteCounter = 0;

	    if (conn->co_Flags & COF_POST) {
		NNPostBuffer(conn);
	    } else {
		NNFeedOverview(conn);
	    }
	    return;
	}

	/*
	 * unescape dots.  We do not unescape dots for a 
	 * POST, we simply pass the dot-escaping through
	 * all the way to the real posting server.
	 */

	if ((conn->co_Flags & COF_POST) == 0) {
	    if (len > 0 && buf[0] == '.') {
		++buf;
		--len;
	    }
	}

	/*
	 * end of headers ?
	 * control message ?
	 */

	if (conn->co_Flags & COF_INHEADER) {
	    if (strcmp(buf, "\r") == 0) {
		conn->co_Flags &= ~COF_INHEADER;
	    } else if (strncasecmp(buf, "Bytes:", 6) == 0) {
		/*
		 * save received Bytes: header (for header-only feed), but
		 * do not write the header out.
		 */
		conn->co_BytesHeader = strtol(buf + 6, NULL, 10);
		len = 0;
	    } else if (strncasecmp(buf, "Control:", 8) == 0) {
		conn->co_Flags |= COF_WASCONTROL;
	    } else if (strncasecmp(buf, "xref:", 5) == 0) {
		/*
		 * strip XRef: unless it's one we want
		 */
		if (ValidXRef(buf, len) < 0)
		    len = 0;
	    }
	}

	/*
	 * buffer the article, but don't bother buffering the article body
	 * for a fed article.  BUT, if it's a Control: message, we need the
	 * whole body so we can pgp-verify it
	 */

	if ((conn->co_Flags & COF_INHEADER) || (conn->co_Flags & (COF_POST|COF_WASCONTROL))) {
	    if (len > 0)
		buf[len-1] = '\n';
	    conn->co_ByteCounter += len;
	    MBWrite(&conn->co_ArtBuf, buf, len);
	}
    }
    if (len <= 0) {
	if (len < 0)
	    NNTerminate(conn);
    } else {
	/*
	 * more lines were pending, wake us up immediately, after we've
	 * processed some other people.
	 */
	FD_SET(conn->co_Desc->d_Fd, &WFds);
    }
}

/*
 * Return 0 on success, -1 if XRefHost is disabled, -2
 * If the XRef is invalid.
 */

int
ValidXRef(const char *buf, int len)
{
    /*
     * strip XRef: unless it's one we want
     */
    int l;
    int lp;
    const char *p;

    if (XRefHost == NULL)
	return(-1);

    l = strlen(XRefHost);
    p = buf + 5;
    lp = len - 5;

    while (lp > 0 && *p == ' ') {
	--lp;
	++p;
    }
    if (lp <= l || 
	strncasecmp(XRefHost, p, l) != 0 ||
	p[l] != ' '
    ) {
	return(-2);
    }
    return(0);
}

