/*
 * nntp.c Copyright 1999 Christopher M Sedore. All Rights Reserved.
 * Please see the "COPYING" file for license details.
 *
 * This file contains functions which implement NNTP.  The implementation
 * is a simple one, using and array of protocol commands, requires
 * permissions, and a callback function.  
 *
 * These routines are driven via async io through upcalls to NntpMain().
 */

#include "main.h"

int ArtRead(struct context *cc);

void NntpIhave(struct context *cc);
void NntpQuit(struct context *cc);
void NntpCheck(struct context *cc);
void NntpTakethis(struct context *cc);
void NntpModestream(struct context *cc);
void NntpMaint(struct context *cc);
extern void NntpFeedIn(struct context *cc);
void NntpFeedOut(struct context *cc);
void NntpUnknown(struct context *cc);

#define NUM_NNTP_COMMANDS 8

struct _nntpcmds {
	char cmd[20];
	int cmdlen;
	int perm;
	void (*cmdfunc)(struct context *cc);
} nntpCommands[NUM_NNTP_COMMANDS+1]=
{"check",5,PERM_FEED,NntpCheck,
	"ihave",5,PERM_FEED,NntpIhave,
	"takethis",8,PERM_FEED,NntpTakethis,
	"mode stream",11,PERM_FEED,NntpModestream,
	"quit",4,-1,NntpQuit,
	"maint",5,PERM_MAINT,NntpMaint,
	"statsin",7,PERM_MAINT,NntpFeedIn,
	"statsout",8,PERM_MAINT,NntpFeedOut,
	"unknown",7,-1,NntpUnknown};

TAILQ_HEAD(consumerlist,artconsumer) consumerhead;
	
extern int externalFilterActive;

u_quad_t
HashArticleID(char *mid,int length)
{
	char *a;
	MD5_CTX ctx;
	u_quad_t md5buf[2];
	
	
	if (length==0) {
		a=mid;
		while ((a-mid<255) && (*a!='>')) a++;
		
		if (a-mid==255)
			return 0;
		a++;
		length=(a-mid)-1;
	}
	
	MD5Init(&ctx);
	MD5Update(&ctx,mid,length);
	MD5Final((char *)&md5buf,&ctx);
	
	return md5buf[0];
}	

/* 
 * GetArticleID()
 *
 * Try to find a message-id searching forward through cc->bp for
 * "<" some chars ">".  We're not very picky, but modificaitons
 * to this function could make us so.
 */

int
GetArticleID(struct context *cc) 
{
	char *a,*b;
	
	
	a=cc->bp;
	while ((*a!='<') && (*a) ) {a++;}
	
	if (!*a) {
		return -1;
	}
	
	b=a;
	
	while ((*b!='>') && (*b) && (b-a<MAX_ARTICLEID)) {
		cc->mid[b-a]=*b;
		b++;
	}
	
	if ((b-a<3) || (b-a==MAX_ARTICLEID) || (!*b))
		return -1;
	
	cc->mid[b-a]=*b;
	b++;
	
	cc->mid[b-a]=0;
	
	cc->mid64=HashArticleID(cc->mid,(b-a)-1);
	
	return 0;
}

/*
 * RegisterArticleConsumer() is used to hook into the article arrival
 * process.  When an article is received, it is passed through
 * the consumers and they can specify whether they have "consumed"
 * the article, that we shoud reject it, or that they don't care
 * about it (though a consumer who ignores may modify the article
 * in place).
 *
 * Setting pri to 0 indicates that you want to be inserted at the 
 * head of the consumer chain, 1 indicates putting you on the end.
 * Order matters, since once an article is consumed, no other
 * consumers will see it.
 */

void
RegisterArticleConsumer(int (*callback)(struct article *art),int pri)
{
	struct artconsumer *ac;
	
	assert(callback!=NULL);
	
	ac=malloc(sizeof(struct artconsumer));
	
	bzero(ac,sizeof(struct artconsumer));
	
	ac->callback=callback;
	
	if (!pri) {
		TAILQ_INSERT_HEAD(&consumerhead,ac,next);
	} else {
		TAILQ_INSERT_TAIL(&consumerhead,ac,next);
	}
}

/*
 * PrepareToReadArticle() is called to prepare to read an article on
 * the specified connection.  This function allocates memory, sets
 * flags, and prepares the article structure.
 */

void
PrepareToReadArticle(struct context *cc)
{
	char *s=NULL;
	
	
	cc->art=malloc(sizeof(struct article));
	bzero(cc->art,sizeof(struct article));
	cc->art->len=0;
	cc->art->bufsz=BASE_ARTSIZE;
	cc->art->bufp=malloc(BASE_ARTSIZE);
	cc->art->buf=cc->art->bufp+255;
	cc->flags|=NNTP_READ_ARTICLE;
	strcpy(cc->art->mid,cc->mid);
	cc->callback=ArtRead;
	cc->state="readart";
	cc->flags&=~NNTP_BAD_ARTICLE;	
	assert(*cc->art->mid=='<');
}

/*
 * CleanupAfterReadArticle() is called to free memory allocated
 * by PrepareToReadArticle()
 */

void
CleanupAfterReadArticle(struct article *art)
{
	FreeMatchData(art);
	free(art->bufp);
	free(art);
}

/*
 * FindConsumer() passes the article to consumers until it a) is consumed
 * b) is rejected, or c) runs out of consumers.
 * 
 * FindConsumer() is also responsible for making sure we haven't 
 * already received the article, and that if there is an incoming
 * distribution, that the article would pass through it.
 */

void
FindConsumer(struct context *cc) 
{
	struct artconsumer *ac;
	int df,res,none=1;
	
	if (!AddSeen(cc->mid64,0)) {
//		printf("got already\n");
		cc->flags|=NNTP_BAD_ARTICLE;
		CleanupAfterReadArticle(cc->art);
		return;
	}
	
	if (cc->incoming->dist) {
		if (!CheckMatch(cc->art,cc->incoming->dist)) {
			printf("dist mismatch\n");
			cc->flags|=NNTP_BAD_ARTICLE;
			CleanupAfterReadArticle(cc->art);
			return;
		}
	}	
	
	for (ac=TAILQ_FIRST(&consumerhead);ac;ac=TAILQ_NEXT(ac,next)) {
		df=1;
		if (ac->dist) {
			df=CheckMatch(cc->art,ac->dist);
		}	
		
		if (df) {
			assert(ac->callback!=NULL);
			res=ac->callback(cc->art);
			if (res==CONSUMER_OK) {
				none=0;
				break;
			}
			if (res==CONSUMER_REJECT) {
				cc->flags|=NNTP_BAD_ARTICLE;
				break;
			}
			if (res==CONSUMER_IGNORE)
				continue;
		}
	}
	
	if (none)
		CleanupAfterReadArticle(cc->art);
}

/*
 * ArtRead() is the callback function setup when we transition to reading
 * an article on an incoming connection.  ArtRead() will check to make
 * sure the article read was not flagged bad, and then call
 * FindConsumer().  It also generates the NNTP response.
 */

int
ArtRead(struct context *cc) {
	
	cc->flags&=~NNTP_READ_ARTICLE;
	cc->state="artproc";
	if (!(cc->flags & NNTP_BAD_ARTICLE)){
		if (PrepareForMatch(cc->art)) {
			cc->state="procart";
			if (externalFilterActive) {
				ArtExternalFilter(cc);
				return -1;
			} else 
				FindConsumer(cc);
		} else {
			cc->flags|=NNTP_BAD_ARTICLE;
//			printf("prepare failed\n");
			CleanupAfterReadArticle(cc->art);
			cc->art=NULL;
		}
	} else {
		cc->flags|=NNTP_BAD_ARTICLE;
		printf("bad art\n");
		CleanupAfterReadArticle(cc->art);
		cc->art=NULL;
	}
	
	return ArtReadRespond(cc);
	
}

int 
ArtReadRespond(struct context *cc)
{
	if (cc->flags & NNTP_BAD_ARTICLE) {
		cc->incoming->reject++;
		if (cc->flags & NNTP_STREAMING) {
			sprintf(&cc->obuf[cc->obuflen],
				"439 %s\r\n",cc->mid);
			cc->obuflen+=strlen(&cc->obuf[cc->obuflen]);
		} else {
			memcpy(&cc->obuf[cc->obuflen],"437\r\n",5);
			cc->obuflen+=5;
		}
	} else {
		cc->incoming->accept++;
		if (cc->flags & NNTP_STREAMING) {
			sprintf(&cc->obuf[cc->obuflen],
				"239 %s\r\n",cc->mid);
			cc->obuflen+=strlen(&cc->obuf[cc->obuflen]);
		} else {
			memcpy(&cc->obuf[cc->obuflen],"235\r\n",5);
			cc->obuflen+=5;
		}
	}
	
	
	cc->state="nextcmd";
	cc->callback=NntpMain;
	
	return 0;
}

/*
 * NntpUnknown() is the "default" command function--a fallthrough
 * that generates an error.
 */

void
NntpUnknown(struct context *cc)
{
	memcpy(&cc->obuf[cc->obuflen],"500 what?\r\n",11);
	cc->obuflen+=11;
	printf("unk: [%s] %u\n",cc->bp,cc->buflen);
}

/*
 * Nntp*() functions that implement NNTP commands.
 */

void
NntpQuit(struct context *cc)
{
	memcpy(&cc->obuf[cc->obuflen],"205 Bye\r\n",9);
	cc->obuflen+=9;
	cc->flags|=NNTP_CLOSE_CONNECTION_AFTER_OP;
}

void
NntpIhave(struct context *cc)
{
	if (GetArticleID(cc)) {
		memcpy(&cc->obuf[cc->obuflen],"437 bad msg id\r\n",16);
		cc->obuflen+=16;
		return;
	} else {
		if (!CheckSeen(cc->mid64)) {
			if (AddPrecommit(cc->mid64)) {
				memcpy(&cc->obuf[cc->obuflen],"335\r\n",5);
				cc->obuflen+=5;
				cc->flags&=~NNTP_STREAMING;
				PrepareToReadArticle(cc);
				return;
			}
		} 
		
		memcpy(&cc->obuf[cc->obuflen],"435\r\n",5);
		cc->incoming->refuse++;
		cc->obuflen+=5;
	}
}

void 
NntpCheck(struct context *cc)
{
	if (GetArticleID(cc)) {
		memcpy(&cc->obuf[cc->obuflen],"439 bad msg id\r\n",16);
		cc->obuflen+=16;
		cc->incoming->reject++;
		return;
	} else {
		if (!CheckSeen(cc->mid64)) {
			if (AddPrecommit(cc->mid64)) {
				sprintf(&cc->obuf[cc->obuflen],"238 %s\r\n",cc->mid);
				cc->obuflen+=strlen(&cc->obuf[cc->obuflen]);
				return;
			}
		} 
		cc->incoming->refuse++;
		sprintf(&cc->obuf[cc->obuflen],"438 %s\r\n",cc->mid);
		cc->obuflen+=strlen(&cc->obuf[cc->obuflen]);
	}
}

void 
NntpTakethis(struct context *cc)
{
	if (GetArticleID(cc)) {
		sprintf(&cc->obuf[cc->obuflen],"439 %s\r\n",cc->mid);
		cc->obuflen+=strlen(&cc->obuf[cc->obuflen]);
		return;
	} else {
		AddPrecommit(cc->mid64);
		cc->flags|=NNTP_STREAMING;
		PrepareToReadArticle(cc);
	}
}

void
NntpModestream(struct context *cc)
{
	memcpy(&cc->obuf[cc->obuflen],"203 StreamOK\r\n",14);
	cc->obuflen+=14;
}	

/*
 * NntpMaint() is a half-baked, half-completed online config method.
 * It should be yanked.
 */

void
NntpMaint(struct context *cc) 
{
	char *u,*p;
	
	NntpUnknown(cc);
	return;
	
	u=strtok(cc->bp,"\t ");
	p=strtok(NULL,"\t ");
	
	cc->flags|=NNTP_MAINTENANCE;
	sprintf(&cc->obuf[cc->obuflen],"200 maint mode enabled\r\n");
	cc->obuflen+=strlen(&cc->obuf[cc->obuflen]);
	
}

/*
 * NntpMain() is the main upcall routine for incoming data from the 
 * connection.  It searches the array of NNTP commands and executes
 * a callback to the appropriate function.
 */

int
NntpMain(struct context *cc)
{
	char *s;
	int x,lr;
	
	cc->state="nntp";
	
	x=0;
	while (x<NUM_NNTP_COMMANDS) {
		if ((!strncasecmp(cc->bp,nntpCommands[x].cmd,
					nntpCommands[x].cmdlen)) &&
			(cc->flags & nntpCommands[x].perm)) {
			nntpCommands[x].cmdfunc(cc);
			break;
		}
		x++;
	}
	
	if (x==NUM_NNTP_COMMANDS) {
		nntpCommands[x].cmdfunc(cc);
	}
	
	return 0;		
}


int
NntpInit()
{
	TAILQ_INIT(&consumerhead);
}
