/*
 * NNTP.C	- general nntp reader commands
 *
 *	Reader-specific 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 NNTPQuit(Connection *conn, char **pptr);
Prototype void NNTPAuthInfo(Connection *conn, char **pptr);
Prototype void NNTPArticle(Connection *conn, char **pptr);
Prototype void NNTPHead(Connection *conn, char **pptr);
Prototype void NNTPBody(Connection *conn, char **pptr);
Prototype void NNTPDate(Connection *conn, char **pptr);
Prototype void NNTPGroup(Connection *conn, char **pptr);
Prototype void NNTPLast(Connection *conn, char **pptr);
Prototype void NNTPNext(Connection *conn, char **pptr);
Prototype void NNTPStat(Connection *conn, char **pptr);
Prototype void NNExecuteOnRange(Connection *conn, const char *artid, int mode);
Prototype int GoodRC(Connection *conn);
Prototype const char *GoodResId(Connection *conn);
Prototype void DumpOVHeaders(Connection *conn, const char *ovdata, int ovlen);

void NNArticleRetrieve(Connection *conn, const char *artid, int mode);
void NNArticleRetrieveByArtNo(Connection *conn, int artNo);

void
NNTPQuit(Connection *conn, char **pptr)
{
     /*
      * Print closing banner
      */
     MBLogPrintf(conn, &conn->co_TMBuf, "205 Transferred %.0f bytes in %lu article%s, %lu group%s.  Disconnecting.\r\n", conn->co_ClientTotalByteCount, conn->co_ClientTotalArticleCount, conn->co_ClientTotalArticleCount != 1 ? "s" : "", conn->co_ClientGroupCount, conn->co_ClientGroupCount != 1 ? "s" : "");
     NNTerminate(conn);
}

void
NNTPAuthInfo(Connection *conn, char **pptr)
{
    char *type = parseword(pptr, " \t");
    char *args = (type) ? parseword(pptr, " \t") : NULL;
    int ok = 0;

    if (type && args && strlen(args) < 64) {
	if (strcasecmp(type, "user") == 0) {
	    strncpy(conn->co_Auth.dr_AuthUser, args,
				sizeof(conn->co_Auth.dr_AuthUser) - 1);
	    conn->co_Auth.dr_AuthUser[sizeof(conn->co_Auth.dr_AuthUser) - 1] = '\0';
	    ok = 1;
	    conn->co_Auth.dr_Flags |= DF_AUTHREQUIRED;
	    MBLogPrintf(conn, &conn->co_TMBuf, "381 PASS required\r\n");
	} else if (strcasecmp(type, "pass") == 0 &&
				*conn->co_Auth.dr_AuthUser) {
            strncpy(conn->co_Auth.dr_AuthPass, args,
				sizeof(conn->co_Auth.dr_AuthPass) - 1);
	    conn->co_Auth.dr_AuthPass[sizeof(conn->co_Auth.dr_AuthPass) - 1] = '\0';
	    conn->co_Auth.dr_Flags &= ~DF_AUTHREQUIRED;
	    conn->co_Auth.dr_ResultFlags = DR_REQUIRE_DNS;
	    return;
	}
    }
    if (ok == 0)
	MBLogPrintf(conn, &conn->co_TMBuf, "501 user Name|pass Password\r\n");
    if (ok > 0)
	NNCommand(conn);
}

void 
NNTPArticle(Connection *conn, char **pptr)
{
    NNArticleRetrieve(conn, parseword(pptr, " \t"), COM_ARTICLEWVF);
}

void 
NNTPHead(Connection *conn, char **pptr)
{
    NNArticleRetrieve(conn, parseword(pptr, " \t"), COM_HEAD);
}

void 
NNTPBody(Connection *conn, char **pptr)
{
    NNArticleRetrieve(conn, parseword(pptr, " \t"), COM_BODYWVF);
}

void
GroupStats(Connection *conn)
{
    if ((conn->co_GroupName != NULL) &&
	(conn->co_Auth.dr_Flags & DF_GROUPLOG)) {

	char statbuf[1024];

        snprintf(statbuf, sizeof(statbuf), "group %s articles %lu bytes %.0f", conn->co_GroupName, conn->co_ClientGroupArticleCount, conn->co_ClientGroupByteCount);
	LogCmd(conn, '$', statbuf);
	if (conn->co_ClientGroupArticleCount)
            logit(LOG_INFO, "info %s%s%s%s%s %s",
                *conn->co_Auth.dr_AuthUser ? conn->co_Auth.dr_AuthUser : "",
                *conn->co_Auth.dr_AuthUser ? "/" : "",
                *conn->co_Auth.dr_IdentUser ? conn->co_Auth.dr_IdentUser : "",
                *conn->co_Auth.dr_IdentUser ? "@" : "",
		conn->co_Auth.dr_Host,
                statbuf);
	conn->co_ClientGroupArticleCount = 0;
	conn->co_ClientGroupByteCount = 0.0;
    }
}

void 
NNTPGroup(Connection *conn, char **pptr)
{
    char *group = parseword(pptr, " \t");
    int recLen;
    int artBeg;
    int artEnd;
    int adjustedArtBeg = 0;
    const char *rec;
    OverInfo *ov;
    struct GroupList *groups = conn->co_Auth.dr_GroupDef->gr_Groups;

    ++conn->co_Auth.dr_GrpCount;

    if (group == NULL || strlen(group) > MAXGNAME || 
	parseword(pptr, " \t") != NULL
    ) {
	MBLogPrintf(conn, &conn->co_TMBuf, "501 newsgroup\r\n");
	NNCommand(conn);
	return;
    }
    if (groups != NULL && !GroupFindWild(group, groups)) {
	MBLogPrintf(conn, &conn->co_TMBuf, "411 No such group %s\r\n", group);
	NNCommand(conn);
	return;
    }
    if ((rec = KPDBReadRecord(KDBActive, group, KP_LOCK, &recLen)) == NULL) {
	MBLogPrintf(conn, &conn->co_TMBuf, "411 No such group %s\r\n", group);
	NNCommand(conn);
	return;
    }
    artBeg = strtol(KPDBGetField(rec, recLen, "NB", NULL, "-1"), NULL,10);
    artEnd = strtol(KPDBGetField(rec, recLen, "NE", NULL, "-1"), NULL,10);
    KPDBUnlock(KDBActive, rec);

    /*
     * Handle range fixup
     */

    if (artBeg > artEnd) {
	artBeg = artEnd;
	adjustedArtBeg = 1;
    }

    if ((ov = GetOverInfo(group)) != NULL) {
	if (artBeg < artEnd - ov->ov_Head->oh_MaxArts) {
	    artBeg = artEnd - ov->ov_Head->oh_MaxArts;
	    adjustedArtBeg = 1;
	}
	PutOverInfo(ov);
    } else {
	if (artBeg < artEnd - DEFMAXARTSINGROUP) {
	    artBeg = artEnd - DEFMAXARTSINGROUP;
	    adjustedArtBeg = 1;
	}
    }
    if (artEnd == 0 && artBeg != 1) {
	artBeg = 1;
	adjustedArtBeg = 1;
    }

    GroupStats(conn);
    conn->co_ClientGroupCount++;

    zfreeStr(&conn->co_MemPool, &conn->co_GroupName);
    conn->co_GroupName = zallocStr(&conn->co_MemPool, group);

    MBLogPrintf(conn, &conn->co_TMBuf, "211 %d %d %d %s\r\n",
	artEnd - artBeg + 1,
	artBeg,
	artEnd,
	group
    );
    conn->co_ArtNo = artBeg;
    conn->co_ArtBeg = artBeg;
    conn->co_ArtEnd = artEnd;
    NNCommand(conn);
}

void 
NNTPLast(Connection *conn, char **pptr)
{
    int saveArtNo = conn->co_ArtNo;

    if (parseword(pptr, " \t") != NULL) {
	MBLogPrintf(conn, &conn->co_TMBuf, "501 Usage error\r\n");
	NNCommand(conn);
	return;
    }

    while (conn->co_ArtNo > conn->co_ArtBeg) {
	const char *ovdata;
	const char *msgid;
	int ovlen;

	--conn->co_ArtNo;
	if ((ovdata = NNRetrieveHead(conn, &ovlen, &msgid, NULL, NULL, NULL)) != NULL) {
	    MBLogPrintf(conn, &conn->co_TMBuf, 
		"223 %d %s Article retrieved; request text separately.\r\n", 
		conn->co_ArtNo,
		msgid
	    );
	    NNCommand(conn);
	    return;
	}
    }
    MBLogPrintf(conn, &conn->co_TMBuf, "422 No previous to retrieve.\r\n");
    conn->co_ArtNo = saveArtNo;
    NNCommand(conn);
}

void 
NNTPNext(Connection *conn, char **pptr)
{
    int saveArtNo = conn->co_ArtNo;

    if (parseword(pptr, " \t") != NULL) {
	MBLogPrintf(conn, &conn->co_TMBuf, "501 Usage error\r\n");
	NNCommand(conn);
	return;
    }

    while (conn->co_ArtNo < conn->co_ArtEnd) {
	const char *ovdata;
	const char *msgid;
	int ovlen;

	++conn->co_ArtNo;
	if ((ovdata = NNRetrieveHead(conn, &ovlen, &msgid, NULL, NULL, NULL)) != NULL) {
	    MBLogPrintf(conn, &conn->co_TMBuf, 
		"223 %d %s Article retrieved; request text separately.\r\n", 
		conn->co_ArtNo,
		msgid
	    );
	    NNCommand(conn);
	    return;
	}
    }
    MBLogPrintf(conn, &conn->co_TMBuf, "421 No next to retrieve.\r\n");
    conn->co_ArtNo = saveArtNo;
    NNCommand(conn);
}

void 
NNTPStat(Connection *conn, char **pptr)
{
    NNArticleRetrieve(conn, parseword(pptr, " \t"), COM_STAT);
}

void
NNArticleRetrieve(Connection *conn, const char *artid, int mode)
{
    conn->co_ArtMode = mode;

    ++conn->co_Auth.dr_ArtCount;

    conn->co_RequestFlags = ARTFETCH_MSGID;
    if (artid == NULL) {
	if (conn->co_GroupName == NULL) {
	    MBLogPrintf(conn, &conn->co_TMBuf, "412 Not in a newsgroup\r\n");
	    NNCommand(conn);
	    return;
	}
	NNArticleRetrieveByArtNo(conn, conn->co_ArtNo);
    } else if (isdigit((int)artid[0])) {
	if (conn->co_GroupName == NULL) {
	    MBLogPrintf(conn, &conn->co_TMBuf, "412 Not in a newsgroup\r\n");
	    NNCommand(conn);
	    return;
	}
	NNArticleRetrieveByArtNo(conn, strtol(artid, NULL, 10));
    } else {
	artid = MsgId(artid, NULL);
	if (strcmp(artid, "<>") == 0) {
	    MBLogPrintf(conn, &conn->co_TMBuf, "430 No such article\r\n");
	    NNCommand(conn);
	} else {
	    /*
	     * XXX this isn't clean.  We have to turn off verify mode
	     * when retrieving articles by message-id.  It works anyway.
	     * We also use WVF to ensure we don't use headers for
	     * retrieval by artno and not msgid
	     */
	    if (mode == COM_ARTICLEWVF)
		conn->co_ArtMode = COM_ARTICLE;
	    NNArticleRetrieveByMessageId(conn, artid, 0, 0, -1, 0);
	}
    }
}

void
NNArticleRetrieveByArtNo(Connection *conn, int artNo)
{
    const char *ovdata;
    const char *msgid;
    int ovlen, timeRcvd=0, grpIter=-1, endNo=0;

    if (DebugOpt)
	printf("ArtNo %d\n", artNo);

    conn->co_RequestFlags = ARTFETCH_ARTNO;
    conn->co_ArtNo = artNo;
    if ((ovdata = NNRetrieveHead(conn, &ovlen, &msgid, &timeRcvd, &grpIter, &endNo)) != NULL) {
	
	/*
	 * COM_ARTICLEWVF requires that we verify that we have a valid article
	 * body prior to dumping a valid response code.  At this point we only
	 * have headers.  COM_ARTICLE allows us to dump the status & headers
	 * and then dump (article not available) as the body if we cannot 
	 * retrieve the article.
	 */

	if (conn->co_ArtMode != COM_ARTICLEWVF &&
	    conn->co_ArtMode != COM_BODYWVF
	) {
	    /*
	     * Dump status header
	     */

	    MBLogPrintf(conn, &conn->co_TMBuf, 
		"%03d %d %s %s\r\n", 
		GoodRC(conn),
		conn->co_ArtNo,
		msgid,
		GoodResId(conn)
	    );
	}

	/*
	 * Dump headers.  Do not dump them yet for COM_ARTICLEWVF.
	 */
	if (conn->co_ArtMode == COM_HEAD || conn->co_ArtMode == COM_ARTICLE) {
	    DumpOVHeaders(conn, ovdata, ovlen);
	}

	/*
	 * Blank line if both header & body (article)
	 */
	if (conn->co_ArtMode == COM_ARTICLE)
	    MBPrintf(&conn->co_TMBuf, "\r\n");

	/*
	 * Dump body (for COM_ARTICLEWVF, this also dumps the headers when
	 * we have verified that the body can be retrieved)
	 */

	if (conn->co_ArtMode == COM_ARTICLEWVF ||
	    conn->co_ArtMode == COM_BODYWVF
	) {
	    NNArticleRetrieveByMessageId(conn, msgid, 1, timeRcvd, grpIter, endNo);
	} else if (
	    conn->co_ArtMode == COM_ARTICLE || 
	    conn->co_ArtMode == COM_BODY
	) {
	    conn->co_ArtMode = COM_BODYNOSTAT;
	    NNArticleRetrieveByMessageId(conn, msgid, 1, timeRcvd, grpIter, endNo);
	} else {
	    if (conn->co_ArtMode != COM_STAT)
		MBPrintf(&conn->co_TMBuf, ".\r\n");
	    NNCommand(conn);
	}
    } else {
	MBLogPrintf(conn, &conn->co_TMBuf, "423 No such article number in this group\r\n");
	NNCommand(conn);
    }
}

void 
NNExecuteOnRange(Connection *conn, const char *artid, int mode)
{
    printf("ExecuteOnRange not implemented yet\n");
}

int
GoodRC(Connection *conn)
{
    switch(conn->co_ArtMode) {
    case COM_STAT:
	return(223);
    case COM_HEAD:
	return(221);
    case COM_ARTICLE:
    case COM_ARTICLEWVF:
	conn->co_ClientTotalArticleCount++;
	conn->co_ClientGroupArticleCount++;
	return(220);
    case COM_BODY:
    case COM_BODYWVF:
	conn->co_ClientTotalArticleCount++;
	conn->co_ClientGroupArticleCount++;
	return(222);
    }
    return(0);
}

const char *
GoodResId(Connection *conn)
{
    switch(conn->co_ArtMode) {
    case COM_STAT:
	return("status");
    case COM_HEAD:
	return("head");
    case COM_ARTICLE:
    case COM_ARTICLEWVF:
	return("article");
    case COM_BODY:
    case COM_BODYWVF:
	return("body");
    }
    return("?");
}

void
DumpOVHeaders(Connection *conn, const char *ovdata, int ovlen)
{
    char *vserver;
    int vslen;

    if (conn->co_Auth.dr_VServerDef)
	vserver = conn->co_Auth.dr_VServerDef->vs_ClusterName;
    else
	vserver = "";
    vslen = strlen(vserver);

    while (ovlen) {
	int i;
	char ch;

	for (i = 0; i < ovlen && ovdata[i] != '\n'; ++i)
	    ;
	if (i == 1 && ovdata[0] == '\r' && ovdata[1] == '\n')
	    break;
	if (ovdata[0] == '.')
	    MBPrintf(&conn->co_TMBuf, ".");
	ch = tolower(ovdata[0]);
	if (*vserver && ch == 'p' && strncasecmp(ovdata, "Path:", 5) == 0) {
	    int b = 5;
	    char newpath[256];

	    sprintf(newpath, "%s!", vserver);

	    while (b < i && (ovdata[b] == ' ' || ovdata[b] == '\t'))
		++b;

	    MBWrite(&conn->co_TMBuf, ovdata, b);
	    i -= b;
	    ovlen -= b;
	    ovdata += b;
	    if (strncmp(newpath, ovdata, vslen + 1) != 0)
		MBWrite(&conn->co_TMBuf, newpath, vslen + 1);
	} else if (*vserver && ch == 'x' && strncasecmp(ovdata, "Xref:", 5) == 0) {
	    int b = 5;

	    while (b < i && (ovdata[b] == ' ' || ovdata[b] == '\t'))
		++b;

	    MBWrite(&conn->co_TMBuf, ovdata, b);
	    MBWrite(&conn->co_TMBuf, vserver, vslen);

	    while (b < i && (ovdata[b] != ' ' && ovdata[b] != '\t'))
		++b;

	    i -= b;
	    ovlen -= b;
	    ovdata += b;
	}
	MBWrite(&conn->co_TMBuf, ovdata, i);
	if (i == 0 || ovdata[i-1] != '\r')
	    MBWrite(&conn->co_TMBuf, "\r\n", 2);
	else
	    MBWrite(&conn->co_TMBuf, "\n", 1);
	if (i < ovlen)
	    ++i;
	ovlen -= i;
	ovdata += i;
    }
}



syntax highlighted by Code2HTML, v. 0.9.1