/*
 * SPOOL.C	- Spool server state machine
 *
 * (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 NNSpoolCommand1(Connection *conn);

void NNSpoolResponse1(Connection *conn);
void NNSpoolResponse2(Connection *conn);
void NNSpoolResponse3(Connection *conn);
void NNSpoolResponseScrap(Connection *conn);
void NNGetLocal1(Connection *conn);

void
NNSpoolCommand1(Connection *conn)
{
    ServReq *sreq = conn->co_SReq;

    if (DebugOpt)
	printf("NNSpoolCommand1: article %s\n", sreq->sr_MsgId);

    if (conn->co_Desc->d_LocalSpool) {
	if (strcmp(conn->co_Desc->d_LocalSpool, "/") == 0)
	    MBPrintf(&conn->co_TMBuf, "whereis %s\r\n", sreq->sr_MsgId);
	else
	    MBPrintf(&conn->co_TMBuf, "whereis %s REL\r\n", sreq->sr_MsgId);
	NNGetLocal1(conn) ;
    } else {
	MBPrintf(&conn->co_TMBuf, "article %s\r\n", sreq->sr_MsgId);
	conn->co_ServerArticleRequestedCount++;
	NNSpoolResponse1(conn);
    }
}

/*
 * Retrieve location
 */
void
NNGetLocal1(Connection *conn)
{
    ServReq *sreq = conn->co_SReq;
    char *buf;
    char *ptr;
    char *filename;
    int len;
    int offset;
    int size;

    conn->co_Func = NNGetLocal1;
    conn->co_State = "getlcl1";

    if ((len = MBReadLine(&conn->co_RMBuf, &buf)) > 0) {
	conn->co_ServerByteCount += len;
	if (strtol(buf, NULL, 10) == 223) {
	    /*
	     * sr_CConn may be NULL if client was terminated while
	     * server operation was still in progress.
	     */
	    if (conn->co_SReq->sr_CConn) {
		MBFree(&conn->co_SReq->sr_CConn->co_ArtBuf);
		switch(sreq->sr_CConn->co_ArtMode) {
		    case COM_BODYNOSTAT:
			break;
		    case COM_STAT:
		    case COM_HEAD:
		    case COM_BODY:
		    case COM_ARTICLE:
			MBLogPrintf(sreq->sr_CConn, &sreq->sr_CConn->co_ArtBuf, "%03d 0 %s %s\r\n",
						GoodRC(sreq->sr_CConn),
						sreq->sr_MsgId,
						GoodResId(sreq->sr_CConn)
			);
			break;
		    case COM_BODYWVF:
			MBLogPrintf(sreq->sr_CConn, &sreq->sr_CConn->co_ArtBuf, "%03d %d %s %s\r\n",
						GoodRC(sreq->sr_CConn),
						sreq->sr_CConn->co_ArtNo,
						sreq->sr_MsgId,
						GoodResId(sreq->sr_CConn)
			);
			break;
		    case COM_ARTICLEWVF: {
			const char *ovdata;
			const char *msgid;
			int ovlen;
			if ((ovdata = NNRetrieveHead(sreq->sr_CConn, &ovlen, &msgid, NULL, NULL, NULL)) != NULL) {
			    MBLogPrintf(sreq->sr_CConn, &sreq->sr_CConn->co_TMBuf, "%03d %d %s %s\r\n",
						GoodRC(sreq->sr_CConn),
						sreq->sr_CConn->co_ArtNo,
						sreq->sr_MsgId,
						GoodResId(sreq->sr_CConn)
);
			    DumpOVHeaders(sreq->sr_CConn, ovdata, ovlen);
			    MBPrintf(&sreq->sr_CConn->co_TMBuf, "\r\n"); 
			    sreq->sr_CConn->co_ArtMode = COM_BODYNOSTAT;
			} else {
			    NNSpoolResponseScrap(conn);
			    return; /* bleh */
			}
		    }
		    break;
		}
	    }
	    /*
	     * We have to get additionnal information from the buffer
	     */
	    strtok(buf, " ") ;
	    offset = -1;
	    size = -1;
	    filename = NULL;
	    while ((ptr = strtok(NULL, " ")) != NULL) {
		if (strcasecmp(ptr, "in") == 0) {
		    filename = strtok(NULL," ");
		} else if (strcasecmp("offset", ptr) == 0) {
		    ptr = strtok(NULL," ");
		    if (ptr) {
			offset = strtol(ptr, NULL, 10);
			if (offset == LONG_MIN || offset == LONG_MAX)
			    offset = -1;
		    } else {
			offset = -1;
		    }
		} else if (strcasecmp("length", ptr) == 0) {
		    ptr = strtok(NULL," ");
		    if (ptr) {
			size = strtol(ptr, NULL, 10);
			if (size == LONG_MIN || size == LONG_MAX)
			    size = -1;
		    } else {
			size = -1;
		    }
		}
	    }

	    if (filename != NULL && offset >= 0 && size >= 0) {
		int lf;
		static char filepath[PATH_MAX];
		if (DebugOpt)
		    printf("NNGetLocal1 : whereis returned file %s offset %i size %i\n",
						filename, offset, size);
		if (strcmp(conn->co_Desc->d_LocalSpool, "/") == 0)
		    snprintf(filepath, sizeof(filepath), "%s", filename);
		else
		    snprintf(filepath, sizeof(filepath), "%s/%s",
					conn->co_Desc->d_LocalSpool, filename);
		lf = open(filepath, O_RDONLY);
		if (lf >= 0 && NewDFA(conn, lf, offset, size)) {
		    NNSendLocalArticle(conn);
		} else {
		    if (lf == -1) {
			logit(LOG_ERR, "NNGetLocal1 : problem while openning file %s",
							filepath);
		    } else {
			logit(LOG_ERR, "NNGetLocal1 : problem while seeking file %s offset %i",
							filepath, offset);
		    }
		    close(lf);
		    MBPrintf(&conn->co_TMBuf, "article %s\r\n", sreq->sr_MsgId);
		    NNSpoolResponse1(conn);
		}
	    } else {
		logit(LOG_ERR, "NNGetLocal1 : error while getting whereis informations");
		MBPrintf(&conn->co_TMBuf, "article %s\r\n", sreq->sr_MsgId);
		NNSpoolResponse1(conn);
	    }
	} else {
	    /*
	     * whereis request failed, try the normal way
	     */
	    MBPrintf(&conn->co_TMBuf, "article %s\r\n", sreq->sr_MsgId);
	    conn->co_ServerArticleRequestedCount++;
	    NNSpoolResponse1(conn);
	}
    } else if (len < 0) {
	NNServerTerminate(conn);
    } /* else we haven't got the response yet */
}

/*
 * Retrieve return code
 */

void
NNSpoolResponse1(Connection *conn)
{
    ServReq *sreq = conn->co_SReq;
    char *buf;
    int len;

    conn->co_Func = NNSpoolResponse1;
    conn->co_State = "spres1";

    if ((len = MBReadLine(&conn->co_RMBuf, &buf)) > 0) {
	conn->co_ServerByteCount += len;
	if (strtol(buf, NULL, 10) == 220) {
	    /* We have a positive answer, we may cache article */
	    if (conn->co_Desc->d_Cache) {
		CreateCache(conn);
	    }
	    /*
	     * sr_CConn may be NULL if client was terminated while
	     * server operation was still in progress.
	     */
	    if (conn->co_SReq->sr_CConn) {
		MBFree(&conn->co_SReq->sr_CConn->co_ArtBuf);

		switch(sreq->sr_CConn->co_ArtMode) {
		case COM_BODYNOSTAT:
		    break;
		case COM_STAT:
		case COM_HEAD:
		case COM_BODY:
		case COM_ARTICLE:
		    MBLogPrintf(sreq->sr_CConn, &sreq->sr_CConn->co_ArtBuf, "%03d 0 %s %s\r\n",
			GoodRC(sreq->sr_CConn),
			sreq->sr_MsgId,
			GoodResId(sreq->sr_CConn)
		    );
		    break;
		case COM_BODYWVF:
		    MBLogPrintf(sreq->sr_CConn, &sreq->sr_CConn->co_ArtBuf, "%03d %d %s %s\r\n",
			GoodRC(sreq->sr_CConn),
			sreq->sr_CConn->co_ArtNo,
			sreq->sr_MsgId,
			GoodResId(sreq->sr_CConn)
		    );
		    break;
		case COM_ARTICLEWVF:
		    {
			const char *ovdata;
			const char *msgid;
			int ovlen;

			if ((ovdata = NNRetrieveHead(sreq->sr_CConn, &ovlen, &msgid, NULL, NULL, NULL)) != NULL) {
			    MBLogPrintf(sreq->sr_CConn, &sreq->sr_CConn->co_TMBuf, "%03d %d %s %s\r\n",
				GoodRC(sreq->sr_CConn),
				sreq->sr_CConn->co_ArtNo,
				sreq->sr_MsgId,
				GoodResId(sreq->sr_CConn)
			    );
			    DumpOVHeaders(sreq->sr_CConn, ovdata, ovlen);
			    MBPrintf(&sreq->sr_CConn->co_TMBuf, "\r\n"); 
			    sreq->sr_CConn->co_ArtMode = COM_BODYNOSTAT;
			} else {
			    NNSpoolResponseScrap(conn);
			    return; /* bleh */
			}
		    }
		    break;
		}
	    }
	    NNSpoolResponse2(conn);
	    return;
	}
	else if (strtol(buf, NULL, 10) == 430) {
	  conn->co_ServerArticleNotFoundErrorCount++;
	}
	else {
	  conn->co_ServerArticleMiscErrorCount++;
	}

	if (sreq->sr_CConn == NULL)
	    NNFinishSReq(conn, NULL, 0);
	else if (sreq->sr_CConn->co_ArtMode == COM_BODYNOSTAT)
	    NNFinishSReq(conn, "(article not available)\r\n.\r\n", 1);
	else if (sreq->sr_CConn->co_RequestFlags == ARTFETCH_ARTNO)
            NNFinishSReq(conn, "423 No such article number in this group\r\n", 1);
	else
            NNFinishSReq(conn, "430 No such article\r\n", 1);
    } else if (len < 0) {
	NNServerTerminate(conn);
    } /* else we haven't got the response yet */
}

/*
 * Retrieve headers
 */

void
NNSpoolResponse2(Connection *conn)
{
    char *buf;
    int len;
    ServReq *sreq = conn->co_SReq;
    char *vserver;
    int doneconn;
    char ch;

    conn->co_Func = NNSpoolResponse2;
    conn->co_State = "spres2";

    /*
     * We need to check that co_Auth.dr_VServerDef is defined
     * because it probably won't be for a connection to a backend
     * spool
     */
    if (sreq->sr_CConn && sreq->sr_CConn->co_Auth.dr_VServerDef)
	vserver = sreq->sr_CConn->co_Auth.dr_VServerDef->vs_ClusterName;
    else
	vserver = "";

    while ((len = MBReadLine(&conn->co_RMBuf, &buf)) > 0) {
	conn->co_ServerByteCount += len;
	if (len == 2 && strcmp(buf, "\r") == 0) {
	    if (sreq->sr_Cache)
		fwrite("\r\n", 1, 2, sreq->sr_Cache);

	    if (sreq->sr_CConn) {
		switch(sreq->sr_CConn->co_ArtMode) {
		case COM_ARTICLE:
		    MBPrintf(&sreq->sr_CConn->co_ArtBuf, "\r\n");
		    break;
		}
	    }
	    NNSpoolResponse3(conn);
	    return;
	}
	if (len == 3 && strcmp(buf, ".\r") == 0) {
	    if (sreq->sr_CConn == NULL)
		NNFinishSReq(conn, NULL, 0);
	    else if (sreq->sr_CConn->co_ArtMode == COM_BODYNOSTAT)
		NNFinishSReq(conn, "(article not available)\r\n.\r\n", 1);
	    else if (sreq->sr_CConn->co_RequestFlags == ARTFETCH_ARTNO)
		NNFinishSReq(conn, "423 No such article number in this group\r\n", 1);
	    else
		NNFinishSReq(conn, "430 No such article\r\n", 1);
	    return;
	}

	/*
	 * Only munge Xref: header if we asked for it, the wrong Xref:
	 * header can blow up news readers, so we need to be able to
	 * change it when viewing to match the Path:
	 *
	 * sr_CConn may be NULL if the client terminated or if an
	 * autonomous lookahead request was issued.
	 *
	 */

	doneconn = 0;
	ch = tolower(*buf);
	if (*vserver && sreq->sr_CConn &&
		!sreq->sr_CConn->co_Auth.dr_VServerDef->vs_NoXrefHostUpdate &&
		ch == 'x' && strncasecmp(buf, "Xref:", 5) == 0) {
	    char *ptr;
	    int l;

	    /*
	     * The len includes the trailing \n converted to a \0
	     */
	    l = len - 2;
	    while (l > 5 && (buf[l] == '\r' || buf[l] == '\n'))
		l--;

	    ptr = buf + 5;
	    while (isspace((int)*ptr))
		ptr++;
	    while (!isspace((int)*ptr))
		ptr++;
	    while (isspace((int)*ptr))
		ptr++;
	    if (*ptr) {
		char line[8192];
		int e;

		l = (buf + l) - ptr + 1;
		sprintf(line, "Xref: %s ", vserver);
		e = strlen(line);
		memcpy(&line[e], ptr, l);
		line[e + l] = '\0';
		strcat(line, "\r\n");

		switch(sreq->sr_CConn->co_ArtMode) {
		    case COM_HEAD:
		    case COM_ARTICLE:
			MBWrite(&sreq->sr_CConn->co_ArtBuf, line, strlen(line));
			break;
		}
		doneconn = 1;
	    }
	}
	if (*vserver && sreq->sr_CConn &&
		!sreq->sr_CConn->co_Auth.dr_VServerDef->vs_NoReadPath &&
			ch == 'p' && strncasecmp(buf, "Path:", 5) == 0) {
	    char *ptr;
	    int l;
	    int vsl = strlen(vserver);

	    /*
	     * The len includes the trailing \n converted to a \0
	     */
	    l = len - 2;
	    while (l > 5 && (buf[l] == '\r' || buf[l] == '\n'))
		l--;

	    ptr = buf + 5;
	    while (isspace((int)*ptr))
		ptr++;
	    if (*ptr && (strncmp(vserver, ptr, vsl) ||
			 ((ptr[vsl] != '\0') &&
			  (ptr[vsl] != '!')))) {
		char line[8192];
		int e;

		l = (buf + l) - ptr + 1;
		sprintf(line, "Path: %s!", vserver);
		e = strlen(line);
		memcpy(&line[e], ptr, l);
		line[e + l] = '\0';
		strcat(line, "\r\n");

		switch(sreq->sr_CConn->co_ArtMode) {
		    case COM_HEAD:
		    case COM_ARTICLE:
			MBWrite(&sreq->sr_CConn->co_ArtBuf, line, strlen(line));
			break;
		}
		doneconn = 1;
	    }
	}

	if (len) {
	    buf[len-1] = '\n';

	    if (sreq->sr_Cache)
		fwrite(buf, 1, len, sreq->sr_Cache);

	    /*
	     * sr_CConn may be NULL if the client terminated or if an
	     * autonomous lookahead request was issued.
	     */

	    if (!doneconn && sreq->sr_CConn) {
		switch(sreq->sr_CConn->co_ArtMode) {
		case COM_HEAD:
		case COM_ARTICLE:
		    MBWrite(&sreq->sr_CConn->co_ArtBuf, buf, len);
		    break;
		}
	    }
	}

    }
    if (len < 0) {
	NNServerTerminate(conn);
    } /* else we are still waiting for input */
}

/*
 * Retrieve the body, place in client's ArtBuf
 */

void
NNSpoolResponse3(Connection *conn)
{
    char *buf;
    ServReq *sreq = conn->co_SReq;

    conn->co_Func = NNSpoolResponse3;
    conn->co_State = "spres3";

    for (;;) {
	int len = MBReadLine(&conn->co_RMBuf, &buf);
	int error = 0;

	/*
	 * If an error occurs and we are not in FastCopyOpt mode,
	 * we can simply terminate the server and the request will be
	 * retried.  If we are an FastCopyOpt mode we have already 
	 * started writing the article back to the client and cannot
	 * simply cut things off.  We simulate an ending, make sure
	 * we do not commit the article to the cache, and *then*
	 * terminate the server.
	 */

	if (len < 0) {
	    if (FastCopyOpt == 0) {
		NNServerTerminate(conn);
		return;
	    }
	    len = 3;
	    buf = ".\r";
	    error = 1;
	}

	/*
	 * If len is 0, we have nothing to do
	 */

	if (len == 0)
	    break;


	conn->co_ServerByteCount += len + 1;
	if (len == 3 && strcmp(buf, ".\r") == 0) {
	    conn->co_ServerArticleCount++;
	    if (sreq->sr_CConn) {
		MBCopy(
		    &sreq->sr_CConn->co_ArtBuf,
		    &sreq->sr_CConn->co_TMBuf
		);
	    }
	    if (sreq->sr_Cache) {
		fflush(sreq->sr_Cache);
		if (ferror(sreq->sr_Cache) || error)
		    AbortCache(fileno(sreq->sr_Cache), sreq->sr_MsgId, 0);
		else
		    CommitCache(conn, 0);
		fclose(sreq->sr_Cache);
		sreq->sr_Cache = NULL;
	    }
	    if (sreq->sr_CConn == NULL) {
		NNFinishSReq(conn, NULL, 0);
	    } else {
		switch(sreq->sr_CConn->co_ArtMode) {
		case COM_ARTICLE:
		case COM_BODY:
		case COM_BODYWVF:
		case COM_BODYNOSTAT:
		case COM_HEAD:
		    if (error)
			MBPrintf(&sreq->sr_CConn->co_TMBuf, "(spool server died prior to completion of the article dump)\r\n");
		    NNFinishSReq(conn, ".\r\n", 0);
		    break;
		case COM_STAT:
		    NNFinishSReq(conn, "", 0);
		    break;
		default:
		    NNFinishSReq(conn, "(something blew up in the spool code)\r\n.\r\n", 0);
		    break;
		}
	    }

	    /*
	     * If an error occured ( can only happen if we were in FastCopyOpt
	     * mode ), we have to terminate the server now, after we've
	     * finished processing the client request.
	     */
	    if (error) {
		NNServerTerminate(conn);
	    }
	    return;
	}
	if (len > 0)
	    buf[len-1] = '\n';

	if (sreq->sr_CConn) {
	    switch(sreq->sr_CConn->co_ArtMode) {
	    case COM_ARTICLE:
	    case COM_BODY:
	    case COM_BODYWVF:
	    case COM_BODYNOSTAT:
		MBWrite(&sreq->sr_CConn->co_ArtBuf, buf, len);
		break;
	    }
	}
	if (sreq->sr_Cache)
	    fwrite(buf, 1, len, sreq->sr_Cache);
    }
    if (FastCopyOpt && sreq->sr_CConn) {
        MBCopy(
            &sreq->sr_CConn->co_ArtBuf,
            &sreq->sr_CConn->co_TMBuf
        );
    }
    /* still waiting for input */
}

void
NNSpoolResponseScrap(Connection *conn)
{
    char *buf;
    int len;
    ServReq *sreq = conn->co_SReq;

    conn->co_Func = NNSpoolResponse3;
    conn->co_State = "spres3";

    while ((len = MBReadLine(&conn->co_RMBuf, &buf)) > 0) {
	conn->co_ServerByteCount += len + 1;
	if (len == 3 && strcmp(buf, ".\r") == 0) {
	    conn->co_ServerArticleCount++;
	    if (sreq->sr_Cache) {
		fflush(sreq->sr_Cache);
		if (ferror(sreq->sr_Cache))
		    AbortCache(fileno(sreq->sr_Cache), sreq->sr_MsgId, 0);
		else
		    CommitCache(conn, 0);
		fclose(sreq->sr_Cache);
		sreq->sr_Cache = NULL;
	    }
	    if (sreq->sr_CConn == NULL)
		NNFinishSReq(conn, NULL, 0);
	    else if (sreq->sr_CConn->co_RequestFlags == ARTFETCH_ARTNO)
		NNFinishSReq(conn, "423 No such article number in this group\r\n", 1);
	    else
		NNFinishSReq(conn, "430 No such article\r\n", 1);
	    return;
	}
    }
    if (len < 0) {
	NNServerTerminate(conn);
    } /* else we are still waiting for input */
}



syntax highlighted by Code2HTML, v. 0.9.1