/* Copyright (C) 1995 NetWin World, New Zealand, All rights reserved. */

/*
todo:
	Check for channel close everywhere, and exit or sleep as
	appropriate.
	Add keepalive timer so it will die if nothing happens for 30 minutes...

         
	This code is not nice, maybe because I didn't understand how 
	streaming would really work until after I wrote it... or maybe
	streaming both directions is just plain messey!

	Command parameters:
	Xmit sitename  -nostream -at *5
	Transmit file without streaming, every 5 minutes. 
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef unix
#ifndef WIN32
#include <unistd.h>
#endif
#endif
#include "dnews.h"
#include "conf.h"
#include "lib.h"
#include "str.h"
#include "emsg.h"
#include "nntp.h"
#include "xmit.h"
#include "xmit_stub.h"
#ifndef TRUE
#define FALSE 0
#define TRUE (!FALSE)
#endif

void to_hello(char *bf, int sz);
void sendque_body(int cursend);
void here_flush_status(char *bf, int sz);
void xmit_done(int delit);
void send_ifready(void);
void sendque_doneone(int cur);
void send_upto(char *s);
void send_report(void);
void send_ifendtake(void);
void ring_clear(void);

void send_mail(void);
void m_hello(char *s, int sz);
void m_helo_status(char *s, int sz);
void m_from_status(char *s, int sz);
void m_rcpt_status(char *s, int sz);
void m_data_status(char *s, int sz);
void m_body_status(char *s, int sz);
int m_mangle(String *str);

void uucp_close(void);
int uucp_next(void);


enum {S_EMPTY,S_READY,S_IHAVE,S_CHECK,S_SENDING,S_WANTED};
static char *state_str[]={"EMPTY", "READY", "IHAVE", "CHECK"
		, "SENDING", "WANTED" };
#define RINGSIZE 30

enum {	H_GROUP_STATUS,H_ITEM_STATUS,H_ITEM, H_HELLO, H_FLUSH_STATUS
	,T_IHAVE_STATUS,T_ITEM_STATUS, T_HELLO
	,T_STREAM_STATUS, T_CHECK_STATUS, T_TAKETHIS_STATUS
	,M_HELLO,M_HELO_STATUS,M_FROM_STATUS,M_RCPT_STATUS,M_DATA_STATUS
	,M_BODY_STATUS};

#define MAXCHAN 20
static int chan_lasttime[MAXCHAN];
static int chan_state[MAXCHAN];
static int nsent,nread,nrejected,tstart;
void c_print(int chan,char * arg_list, ...);
void append_file(char *dst, char *src);
static int to_ready;
static int stream;
static int nsending,nchecks,nsendque;
char dnews_conf[BFSZ];

void sendque_done(void);
void send_exit(void);
void send_takethis(int i);
void send_ifendcheck(void);
void send_die(void);
void chan_setstate(int c, int v);
void send_ihave(void);
void send_checks(void);
void to_stream_status(char *bf, int sz);
void to_takethis_status(char *bf, int sz);
void to_check_status(char *bf, int sz);
void getque_fill(void);

#define SENDSIZE (RINGSIZE/2)
char *get_que[RINGSIZE+5];

char *send_que[RINGSIZE+5];
int send_state[RINGSIZE+5];
String *send_str[RINGSIZE+5];

static int nget,cursend;
static int chere = -1;
static int cto = -1;
static int lclfail;
static int nostream;

#ifndef BFSZ
#define BFSZ 500
#endif
static char at[BFSZ];
static char sitename[BFSZ];
static char filenames[BFSZ];
static char sendname[BFSZ];
static char requename[BFSZ];
static char uptoname[BFSZ];
static char logname[BFSZ];
char *svc_getargs(char *s);
static int doat;
static int domail;
static char mailaddress[BFSZ];
static char fromname[BFSZ];
static char uudir[BFSZ];
static char myeol[20];
static int isuucp;

#ifdef WIN32
#ifdef DEBUGGING
int ServiceMain(int argc, char *argv[])
#else
int ServiceMain(int xargc, char *xargv[])
#endif
#else
int main(int argc, char *argv[])
#endif
{
	int i;
#ifndef DEBUGGING 
#ifdef WIN32
	int argc;
	char *argv[20];
	char *s,*cmd;

	log_open("xmit.log");

	emsg("xmit started %d\n",xargc);
	log_flush();	
	svc_init(xargv[0]);
	cmd = svc_getargs(xargv[0]);
	emsg("Got args {%s} \n",cmd);
	log_flush();	
	for (argc=0;TRUE;argc++) {
		s = get_word(cmd,argc); if (strlen(s)==0) break;
		argv[argc] = mystrdup(s);
	}
#endif
#endif
	log_flush();	

	strcpy(myeol,"\012");
	for (i=1; i<argc; i++) {
		imsg("args [%d] %s \n",i,argv[i]);
		if (strcmp(argv[i],"-nostream")==0) nostream = TRUE;
		else if (strcmp(argv[i],"-mail")==0) {
			domail = TRUE;
			strcpy(mailaddress,argv[++i]);
		} else if (strcmp(argv[i],"-name")==0) {
			strcpy(filenames,argv[++i]);
		} else if (strcmp(argv[i],"-uucp")==0) {
			strcpy(uudir,argv[++i]);
			isuucp = TRUE;
		} else if (strcmp(argv[i],"-lf")==0) {
			strcpy(myeol,"\012");
		} else if (strcmp(argv[i],"-crlf")==0) {
			strcpy(myeol,"\015\012");
		} else if (strcmp(argv[i],"-from")==0) {
			strcpy(fromname,argv[++i]);
		} else if (strcmp(argv[i],"-at")==0) {
			if (i+2>=argc) {
				emsg("Useage: -at \"mins\" \"hours\" \n");
				return 0;
			}
			doat = TRUE;
			strcpy(at,argv[++i]);
			strcat(at," ");
			strcat(at,argv[++i]);
		}
		else if (strlen(sitename)==0) strcpy(sitename,argv[i]);
		else {emsg("Unrecognized %s \n",argv[i]);}
	}
	if (strlen(sitename)==0) {
		emsg("Usage: xmit site.name -nostream -at mins hours -mail list@x.y.z -uucp c:/uucp  -lf\n");
		return 0;
	}

	imsg("step3 \n");
	if (strlen(filenames)==0) strcpy(filenames,sitename);
	strcpy(logname,feed_fname(filenames,".log"));
	log_open(logname); /*,feed_fname(filenames,".oldlog"));*/
	strcpy(sendname,feed_fname(filenames,".send"));
	strcpy(requename,feed_fname(filenames,".reque"));
	strcpy(uptoname,feed_fname(filenames,".upto"));
	imsg("Sitename {%s} files {%s} send %s\n",sitename,filenames,sendname);

	xmit();
	return 1;
}
static FILE *ffeed,*freque;
int xmit(void)
{

	/* should open an _upto file and scan till that article */
	/* also open a _failed file and re-write failed articles with a count */

	int i;

	for (i=0; i<RINGSIZE+2; i++) get_que[i] = mymalloc(BFSZ);
	for (i=0; i<RINGSIZE+2; i++) send_que[i] = mymalloc(BFSZ);

	send_loop(); /* Doesn't return */
	return TRUE;
}
int reque_open(void)
{
	if (freque != NULL) {
	 	emsg("reque_open is already open \n");
		return TRUE;
	}
	freque = fopen(requename,"a");
	if (freque==NULL) {filemsg("Unable to open %s \n",requename); return FALSE;}
	return TRUE;
}
void reque_close(void)
{
	if (freque!=NULL) fclose(freque);
	freque = NULL;
}
int send_fopen(void)
{
	FILE *f;
	char *s;
	char bf[BFSZ],b2[BFSZ];
	int found = FALSE;

	if (ffeed!=NULL) emsg("Files are alrady open!!!\n");

	imsg("Opening %s \n",sendname);
	reque_open();
	ffeed = fopen(sendname,"r");	
	if (ffeed==NULL) {filemsg("Unable to open %s \n",sendname); return FALSE;}

again:	f = fopen(uptoname,"r");
	if (f==NULL) return TRUE;
	fgets(bf,BFSZ-10,f);
	fclose(f);
	s = strtok(bf," \t\n");
	if (s==NULL) {imsg("Nothing in upto file\n"); return TRUE;}
	strlwr(s);
	if (strcmp(s,"nomsgid")==0) {imsg("Nothing in upto file (nomsgid) \n"); return TRUE;}
	imsg("Restarting at %s \n",s);
	for (;(!feof(ffeed)) ;) { /* Read thru to the 'upto' flag */
		if (fgets(b2,BFSZ-1,ffeed)==NULL) break;
		if (strstr(b2,s)!=NULL) {found=TRUE; break;}
	}
	if (!found) {
		emsg("Could not find restart point %s, reprocessing entire file \n",s);
		fclose(ffeed);
		remove(uptoname);
		goto again;
	}		
	return TRUE;
}
void send_fclose(int delit)
{
	if (ffeed!=NULL) fclose(ffeed); ffeed = NULL;
	reque_close();
	if (delit) {
		imsg("Remove file %s \n",sendname);
		remove(sendname);
		remove(uptoname);
	}
}
int send_start(void)
{
	char bf[BFSZ];
	tstart = lib_gmtime();
	nsent = nread = nrejected = 0;

	wmsg("Local site {%s} \n", config_get(C_MYNAME));
	chere = nntp_open(config_get(C_MYNAME));
	ncpy(bf,sitename,BFSZ-1);
	if (!isuucp) {
		if (domail) strcat(bf,":25");	
		wmsg("Destination  site {%s} \n", bf);
		cto = nntp_open(bf);
		to_ready = FALSE;
		if (cto<0) return FALSE;
		chan_setstate(cto,T_HELLO);
		if (domail) chan_setstate(cto,M_HELLO);
	}
	if (chere<0) return FALSE;
	chan_setstate(chere,H_HELLO); /* Wait for welcome messages */
	return TRUE;
}
int send_loop(void)
{
	sig_init(); /* Trap PIPE errors, rather than crash */

	nntp_startup();
	send_start();
	nntp_wait();   /* DOESN'T RETURN */
	return TRUE;
}
void send_show(void)
{
	int i;
	for (i=0; i<RINGSIZE; i++) {
		imsg("[%d] %s %s ",i,state_str[send_state[i]],send_que[i]);
	}
}
static int doexit;
void suck_setexit(void)
{
	doexit = TRUE;
	doat = FALSE;
}
static int dochecks;
void suck_do(void)	/* Background loop */
{
	static int last_flush;
	int delay = lib_gmtime() - last_flush;

	if (doexit) {
		emsg("Service closedown requested\n");
		xmit_done(FALSE);
	}
	/* if first time, send mode commands to find out if we can stream */
	/* and if we can send smart fetch commands 			  */
	/* Might need to do some startup article commands		  */
	dmsg("nget %d nsendque %d ncheck %d nsending %d \n",nget,nsendque,nchecks,nsending);

	if (delay>10) {log_flush(); last_flush = lib_gmtime();}

	if (!isuucp) if (!nntp_isopen(cto)) cto = -1;
	if (!nntp_isopen(chere)) chere = -1;
	if (isuucp) {
		if ((chere<0)) {
			emsg("Thats odd, channel closed from %d to %d \n",chere,cto);
			xmit_done(FALSE);
		}
	} else {
		if ((cto<0) || (chere<0)) {
			emsg("Thats odd, channel closed from %d to %d \n",chere,cto);
			xmit_done(FALSE);
		}
	}
	if (isuucp) to_ready = TRUE;
	if (to_ready) if ((nget==0) && (nsendque==0) && (nchecks==0) && (nsending==0)) {
		if (ffeed!=NULL) if (feof(ffeed)) {
			emsg("Closing because nget is empty and feof\n");
			xmit_done(TRUE);
		}
	}
	if (isuucp) to_ready = FALSE;
	send_ifready();
}
/* Called when finished sending */
void xmit_done(int delit)
{
	static int last_time= -1;

	imsg("Done xmit\n");
	send_report();
	send_fclose(delit);
	uucp_close();
	if (cto>=0) c_print(cto,"QUIT\015\012");
	if (chere>=0) c_print(chere,"QUIT\015\012");
	if (cto>=0) nntp_write_now(cto);
	if (chere>=0) nntp_write_now(chere);
	sleep(4); /* Give the channels a chance to close properly */
	if (cto>=0) nntp_close(cto); 
	if (chere>=0) nntp_close(chere); 
	if (cto>=0) nntp_doclose(cto); 
	if (chere>=0) nntp_doclose(chere); 
	cto = -1; chere = -1;
	if (doat) {
		imsg("Waiting for next xmit time {%s} \n",at);
		log_flush();
		nget = nsendque = nchecks = nsending = 0;   /* Clear buffers, bit rough!!*/ 
		ring_clear();
again:
		sleep(5);
		for (;!conf_isat(at);) {
			sleep(10);
		}
		if ((lib_gmtime()-last_time)<70) 
			goto again;
		}
		last_time = lib_gmtime();
		
		send_start(); 
		log_flush();
		return;
	}
	send_exit();
}

/*
	Standard NNTP protocol, no extensions on either end.

	chere:
		>group ggg article xxx
		<status code (article follows)
		<status code
		<article
		<.
	cto:
		>ihave xxxx 
		<status code (send article)
		>send_data
		<status code 

chere:
	Ask for RINGSIZE items
	every time we finish with an item comletely, re-fill the ring
	
cto:
	


*/


int chan_read(int c, char *ss, int sz)
{
	int r=0;
	char *here="chere";
	chan_lasttime[c] = lib_gmtime();
	
	if (c==cto) here="cto"; 	
/*
	if (chan_state[c]!=H_ITEM) imsg("[%d:%s] %s ",c,here,ss);
	if (chan_state[c]!=H_ITEM) 
 	  imsg("Chan_read [%d:%s] %s \n",c,here,mode_str[chan_state[c]]); 
*/
	
	switch (chan_state[c]) {
	  case H_GROUP_STATUS:
		if (sz>2) here_group_status(ss,sz);
		break;
	  case H_ITEM_STATUS:
		if (sz>2) here_item_status(ss,sz);
		break;
	  case H_FLUSH_STATUS:
		if (sz>2) here_flush_status(ss,sz);
		break;
	  case H_ITEM:
		here_item(ss,sz);
		break;
	  case T_IHAVE_STATUS:
		if (sz>2) to_ihave_status(ss,sz);
		break;
	  case T_STREAM_STATUS:
		if (sz>2) to_stream_status(ss,sz);
		break;
	  case T_CHECK_STATUS:
		if (sz>2) to_check_status(ss,sz);
		break;
	  case T_TAKETHIS_STATUS:
		if (sz>2) to_takethis_status(ss,sz);
		break;
	  case T_ITEM_STATUS:
		if (sz>2) to_item_status(ss,sz);
		break;
	  case H_HELLO:
		if (sz>2) here_hello(ss,sz);
		break;
	  case T_HELLO:
		if (sz>2) to_hello(ss,sz);
		break;
	  case M_HELLO:
		if (sz>2) m_hello(ss,sz);
		break;
	  case M_HELO_STATUS:
		if (sz>2) m_helo_status(ss,sz);
		break;
	  case M_FROM_STATUS:
		if (sz>2) m_from_status(ss,sz);
		break;
	  case M_RCPT_STATUS:
		if (sz>2) m_rcpt_status(ss,sz);
		break;
	  case M_DATA_STATUS:
		if (sz>2) m_data_status(ss,sz);
		break;
	  case M_BODY_STATUS:
		if (sz>2) m_body_status(ss,sz);
		break;
	}
	return r;
}
static int nntp_err;
static char nntp_status[BFSZ];

void here_hello(char *bf, int sz)
{
	nntp_err = atoi(bf);
	ncpy(nntp_status,bf,200);
	if (nntp_err!=200) {
		emsg("Bad welcome message %d, {%s} \n",nntp_err,nntp_status);
		xmit_done(FALSE);
		return;
	}
	imsg("here_hello received\n");
	chan_setstate(chere,H_FLUSH_STATUS);
	reque_close(); /* so dnews can copy it into the sending file */
	c_print(chere,"flushsite %s\015\012",filenames);
}
void here_flush_status(char *bf, int sz)
{
	nntp_err = atoi(bf);	ncpy(nntp_status,bf,200);
	if (nntp_err!=280) {emsg("Flush failed? {%s} \n",nntp_status); xmit_done(FALSE);}
	imsg("Flush successful %s ",bf);
	chan_setstate(chere,H_ITEM_STATUS);
	if (!send_fopen()) {
		xmit_done(FALSE);
		return;
	}
	getque_fill();
}
void to_hello(char *bf, int sz)
{
	nntp_err = atoi(bf);
	ncpy(nntp_status,bf,200);
	if ((nntp_err!=200) && (nntp_err!=201)) {
		emsg("Bad welcome message %d, %s \n",nntp_err,nntp_status);
		xmit_done(FALSE);
	}
	imsg("to_hello received\n");
	chan_setstate(cto,T_STREAM_STATUS);
	c_print(cto,"MODE STREAM\015\012"); /* See if it can stream? */
}
void m_hello(char *s, int sz)
{
	if (strncmp(s,"220-",4)==0) return; /* wait for last status line */ 
	if (atoi(s)!=220) {
		emsg("Bad mail welcome message {%s} \n",s);
		xmit_done(FALSE);
	}
	c_print(cto,"HELO %s\015\012",config_get(C_MYNAME)); 
	chan_setstate(cto,M_HELO_STATUS);
}
void m_next(void)
{
	sendque_reque(cursend);
	nrejected++;
	send_mail();
	chan_setstate(cto,M_FROM_STATUS);
}
void m_helo_status(char *s, int sz)
{
	dmsg("to-stream-status %s ",s); 
	to_ready = TRUE;
	chan_setstate(cto,M_FROM_STATUS);
	if ((atoi(s)!=250)) {
		emsg("Helo Expecting 250, got %s",s);
		m_next();
	}
}
void m_from_status(char *s, int sz)
{
	if ((atoi(s)!=250)) {
		emsg("FROM: Expecting 250, got %s",s);
		m_next();
		return;
	}
	c_print(cto,"RCPT TO: %s\015\012",mailaddress);
	chan_setstate(cto,M_RCPT_STATUS);
}
void m_rcpt_status(char *s, int sz)
{
	if ((atoi(s)!=250)) {
		emsg("RCPT: Expecting 250, got %s",s);
		m_next();
		return;
	}
	c_print(cto,"DATA\015\012");
	chan_setstate(cto,M_DATA_STATUS);
}
void m_data_status(char *s, int sz)
{
	if ((atoi(s)!=354)) {
		emsg("DATA: Expecting 354, got %s",s);
		m_next();
		return;
	}
	if (!m_mangle(send_str[cursend])) {
	 	m_next();
		return;
	}
	send_state[cursend] = S_WANTED;
	sendque_body(cursend);
	chan_setstate(cto,M_BODY_STATUS);
}
/* change From: header, and junk it if contains header X-Gated-To  */
#define matches(a,b) (strncmp(a,b,strlen(b))==0)
#define EOL "\015\012"

#define addline(xx) {strcpy(xout,xx); strcat(xout,EOL); xout += strlen(xx)+2;}
int m_mangle(String *str)
{
	char *s,*s2;
	char *out;
	char *xout;
	int r = TRUE;
	int donehead = FALSE;
	int gotreply = FALSE;
	char from[BFSZ];

emsg("Mangle called \n");
	strcpy(from,"");

	out = mymalloc(str_len(str)+1000);
	xout = out;
	s = str_s(str);
/*	dmsg("String %s",s); */
	s2 = strstr(s,EOL);
	for (;s2!=NULL;) {
		*s2 = 0;
		/* dmsg("Sss = %s\n",s); */
		if (!donehead) if (strlen(s)==0) {
			donehead = TRUE;
			if (!gotreply) {
			/*	x = strchr(from,':');
				if (x!=NULL) {
					sprintf(bf,"Reply-To%s",x);
					addline(bf);
				}		 	 */
			}
		}
		if (!donehead) {
			/*if (matches(s,"From:")) {strcpy(from,s); goto cont;}*/
			if (matches(s,"Reply-To:")) gotreply = TRUE;
			if (matches(s,"X-Gateway-From:")) {r = FALSE; goto ret;}
		}
		addline(s);
		s = s2 + strlen(EOL);
		s2 = strstr(s,EOL);
	}
	str_copysn(str,out);
		
ret:
emsg("Mangle returniung \n");
	myfree(out);
	return r;
}
void m_body_status(char *s, int sz)
{
	if ((atoi(s)!=250)) {
		emsg("DATA body: Expecting 250, got %s",s);
		m_next();
		return;
	}
	nsent++;
	chan_setstate(cto,M_FROM_STATUS);
	sendque_doneone(cursend);
	send_ifendtake();
	getque_fill();
}
void here_group_status(char *bf, int sz)
{
	nntp_err = atoi(bf);
	ncpy(nntp_status,bf,200);
	if (nntp_err!=211) {
		emsg("Can't read local message group status {%s} \n",nntp_status);
		if (nntp_err!=423) lclfail++;
		chan_setstate(chere,H_ITEM_STATUS);
		return;
	}
	chan_setstate(chere,H_ITEM_STATUS);
}
void here_item_status(char *bf, int sz)
{
	nntp_err = atoi(bf);
	ncpy(nntp_status,bf,200);
	if (nntp_err!=220) {
		emsg("Can't read local message item status {%s} \n",nntp_status);
		if (nntp_err!=423) lclfail++;
		getque_next(); /* Ask for the next one */
		chan_setstate(chere,H_ITEM_STATUS);
		return;
	}
	chan_setstate(chere,H_ITEM);
}
void here_item(char *bf, int sz)
{
	static String *anitem;
	if (anitem==NULL) anitem = str_new();

	if (strcmp(bf,".\015\012")==0) {
		chan_setstate(chere,H_ITEM_STATUS);
		if (isuucp) uucp_send(anitem);
		else sendque_add(anitem);
		str_clear(anitem);
		getque_next(); /* Ask for the next one */
	} else {
		str_adds(anitem,bf,sz);
	}
}
void to_ihave_status(char *bf, int sz)
{
	nntp_err = atoi(bf);
	ncpy(nntp_status,bf,200);
	if (nntp_err!=335) {
		if ((nntp_err==436) || (nntp_err==480)) {
			if (nntp_err==480) imsg("Transfer permission denied on your upstream site\n");
			sendque_reque(cursend);
			imsg("IHAVE retry: %s",nntp_status); 
		} else { 
			imsg("Rejected ihave status %s\n",nntp_status);
		}
		nrejected++;
		sendque_done();
		chan_setstate(cto,T_IHAVE_STATUS);
		return;
	}
	send_state[cursend] = S_WANTED;
	sendque_body(cursend);
	chan_setstate(cto,T_ITEM_STATUS);
}
void to_item_status(char *bf, int sz)
{
	nntp_err = atoi(bf);
	ncpy(nntp_status,bf,200);
	if (nntp_err!=235) {
		if (nntp_err==436) {
			imsg("item retry: %s",nntp_status); 
			sendque_reque(cursend);
		} else { 
			imsg("Rejected item status %s\n",nntp_status);
		}
		nrejected++;
		sendque_done();
		chan_setstate(cto,T_IHAVE_STATUS);
		return;
	}
	nsent++;
	sendque_done();
	chan_setstate(cto,T_IHAVE_STATUS);
}
void sendque_done(void)
{
	if (isuucp) return;
	sendque_doneone(cursend);
	nsending--;
	send_ifready();
}


/*msgid group:num f:retry */
/*
chan_write_s(chan,"480 transfer permission denied\015\012");
chan_write_s(chan,"436 sorry disk full - try again later\015\012");
chan_write_s(chan,"435 article not wanted - do not send it\015\012");
chan_write_s(chan,"335 send article to be transferred.  End with <CR-LF>.<CR-LF>\015\012");

I'd like to stream, 
*/

void getque_add(char *bf)
{
	if (strlen(bf)>2) {
		c_print(chere,"ARTICLE %s:%s\015\012",getque_group(bf),getque_num(bf));
		ncpy(get_que[nget++],bf,BFSZ-1);
	}
}
void getque_next(void)
{
	int i;
	if (nget>0) {
		for (i=0; i<(nget-1); i++) {
			strcpy(get_que[i],get_que[i+1]);
		}
		nget--;
	}

	getque_fill();
}
int send_empty(void)
{
	int i;
	for (i=0; i<RINGSIZE; i++) {
		if (send_state[i]==S_EMPTY) return i;
	}
	emsg("PANIC, NO EMPTY BUFFERS, SOMETHING IS WRONG \n");
	send_show();
	return 0;
}
void ring_clear(void)
{
	int i;
	for (i=0; i<RINGSIZE; i++) {
		send_state[i] = S_EMPTY;
	}
}
int next_ready(void)
{
	int i;
	for (i=0; i<RINGSIZE; i++) {
		if (send_state[i]==S_READY) return i;
	}
	emsg("No more messages to send\n");
	return -1;
}
void sendque_add(String *item)
{
	int cur = send_empty();

	nsendque++;
	/* imsg("Adding send[%d] %s \n",cur,get_que[0]); */
	/* imsg("Message %s \n",str_s(item)); */

	if (send_str[cur]==NULL) send_str[cur] = str_new();
	str_clear(send_str[cur]);
	str_copy(send_str[cur],item);
	ncpy(send_que[cur],get_que[0],BFSZ-1);
	send_state[cur] = S_READY;
	send_ifready();
}
void send_ifready(void)
{
	getque_fill();
	dmsg("Send ifread, nget %d nsendque %d nsending %d nchecks %d \n",nget,nsendque,nsending,nchecks);
	if (!to_ready) {dmsg("Waiting for stream status still...\n"); return;}
	if (nsending>0) {dmsg("Waiting for sending to be empty ...\n"); return;}
	if (nchecks>0) {dmsg("Waiting for nchecks to be empty ...\n"); return;}
	if (nsendque<SENDSIZE ) dmsg("Waiting for nsendque to grow ...\n"); 
 
	if (stream) {
		if (nsendque>=SENDSIZE ) send_checks();
		else if ((nsendque>0) && (nget==0)) send_checks();
	} else {
		if (nsendque>0) send_checks();
	}
}
void sendque_body(int cursend)
{
	/* imsg("Send Message %s \n",str_s(send_str[cursend])); */
	nntp_write(cto,send_str[cursend]);
	c_print(cto,".\015\012");
	if (send_state[cursend] != S_WANTED) emsg("fatal, state wrong %d \n",send_state[cursend]);
	send_state[cursend] = S_SENDING;
}
void send_ihave(void)
{
	cursend = next_ready(); if (cursend<0) return;
	c_print(cto,"IHAVE %s\015\012",getque_mid(send_que[cursend]));
	send_state[cursend] = S_IHAVE;
	nsending++;
}
void send_mail(void)
{
	cursend = next_ready(); if (cursend<0) return;
	c_print(cto,"MAIL FROM: gateway@%s\015\012",config_get(C_MYNAME));
	send_state[cursend] = S_IHAVE;
	nsending++;
}
void sendque_reque(int cur)
{
	emsg("Reque %d {%s} \n",cur,send_que[cur]);
	if (freque==NULL) if (!reque_open()) return;
	fprintf(freque,"%s",send_que[cur]);
}


/*-------------- Streaming specific code ---------------------------------*/
void send_checks(void)
{
	int i;
	if (!to_ready) dmsg("send_checks called, but not ready yet\n"); 
	if (!to_ready) {dochecks = TRUE; return;}
	if (domail) {send_mail(); return;}
	if (!stream) {send_ihave(); return;}
	nchecks = 0;
	for (i=0; i<RINGSIZE && (nchecks<(RINGSIZE/2)); i++) {
	  if (send_state[i] == S_READY) {
		c_print(cto,"CHECK %s\015\012",getque_mid(send_que[i]));
		send_state[i] = S_CHECK;
		nchecks++;
	  }
	}
	chan_setstate(cto,T_CHECK_STATUS);
}
void to_stream_status(char *bf, int sz)
{
	nntp_err = atoi(bf);
	dmsg("to-stream-status %s ",bf); 
	to_ready = TRUE;
	if ((nntp_err!=203) || nostream) {
		if (!nostream) {
			emsg("Oh dear, he doesn't know how to stream\n");
		} else emsg("Using non streaming xfer protocl\n");
		chan_setstate(cto,T_IHAVE_STATUS);
		return;
	}
	stream = TRUE;
	chan_setstate(cto,T_CHECK_STATUS);
}
void to_takethis_status(char *bf, int sz)
{
	int cur = mid_find(bf);
	ncpy(nntp_status,bf,200);
	dmsg("Takethis-status[%d] %s",cur,bf);

	nntp_err = atoi(bf);


	if ((nntp_err==239) && (cur>=0)) {
		nsent++;
	} else if ((nntp_err==400) || (cur<0)) { /* Connection closed (e.g. disk full)  */
		xmit_done(FALSE);
		return;
	} else if ((nntp_err==431) ) { 
		sendque_reque(cur);
		nrejected++;
	} else if (nntp_err==439) {
		imsg("Rejected item status %s",nntp_status);
		nrejected++;
	} else {
		emsg("Unexpected Rejection %s\n",nntp_status);
		nrejected++;
	}
	sendque_doneone(cur);
	send_ifendtake();
	getque_fill();
}
void sendque_doneone(int cur)
{
	if (nread % 50 == 0) send_upto(getque_mid(send_que[cur]));
	if (send_state[cur] != S_SENDING) 
	  if (send_state[cur] != S_CHECK) 
	    if (send_state[cur] != S_WANTED) emsg("fatal, DONE state wrong %d \n",send_state[cur]);
	send_state[cur] = S_EMPTY;
	strcpy(send_que[cur],"");

	nsendque--;
	getque_fill();
}
void to_check_status(char *bf, int sz)
{
	int cur;

	ncpy(nntp_status,bf,200);
	nntp_err = atoi(bf);
	cur = mid_find(bf);
	dmsg("Check-status[%d] %s",cur,bf);
	if (cur<0) {
		emsg("Got message id that I couldn't find?? %s\n",nntp_status); 
		xmit_done(FALSE);
	}
	if (nntp_err==238) {
	  	if (send_state[cur] != S_CHECK) emsg("fatal, DONE state wrong %d \n",send_state[cur]);
		send_state[cur] = S_WANTED;
		send_takethis(cur);
		send_ifendcheck();
		return;
	}
	if (nntp_err==400) { /* Connection closed (e.g. disk full)  */
		xmit_done(FALSE);
		return;
	}
	if ((nntp_err==431)) { 
		imsg("check retry: %s",nntp_status); 
		nrejected++;
		sendque_reque(cur);
		sendque_doneone(cur);
		send_ifendcheck();
		return;
	}
	sendque_doneone(cur);
	send_ifendcheck();
	imsg("Rejected check status %s",nntp_status);
	nrejected++;
	return;
}
void send_upto(char *s)
{
	FILE *f;
	f = fopen(uptoname,"w");
	if (f==NULL) {filemsg("Can't open %s \n",uptoname); return;}
	fprintf(f,"%s\n",s);
	fclose(f);
}
void send_takethis(int i)
{
	c_print(cto,"TAKETHIS %s\015\012",getque_mid(send_que[i]));
	sendque_body(i);
	nsending++;
}
void send_ifendcheck(void)
{
	nchecks--;
	if (nchecks==0) {
		chan_setstate(cto,T_TAKETHIS_STATUS);
	}
}
void send_ifendtake(void)
{
	nsending--;
	if (nsending==0) {
		send_checks();
	}
}

void getque_fill(void)
{
	char b2[BFSZ];
	if (ffeed==NULL) return;
	for (;(!feof(ffeed)) && ((nget+nsendque)<(RINGSIZE-1)) ;) {
		if (fgets(b2,BFSZ-1,ffeed)==NULL) break;
		nread++;
		if ((nread % 50) == 0) send_report();
		getque_add(b2);
	}
	dmsg("getque fill, nget %d \n",nget);
}


/*-- Util routines --------------------------------------------------------*/
int mid_find(char *s)
{
	char bf[BFSZ];	
	int i;
	char *x;
	char *ss=s;
	ncpy(bf,s,BFSZ-1);
	s = strtok(bf," \t\015\012");
	s = strtok(NULL," \t\015\012");
	if (s==NULL) {
		emsg("DUD message id returned %s\n",ss);
		return -1;
	}
	x = strchr(s,'>');
	if (x!=NULL) *(x+1) = 0;
	for (i=0; i<RINGSIZE; i++) {
		if (strcmp(s,getque_mid(send_que[i]))==0) {
			return i;
		}
	}
	emsg("DUD message id returned {%s}\n",ss);
	return -1;
}
void append_file(char *dst, char *src)
{
	FILE *fsrc,*fdst;
	char bf[BFSZ];

	fsrc = fopen(src,"r");
	if (fsrc==NULL) return;
	fdst = fopen(dst,"a");
	if (fdst==NULL) { filemsg("Can't open output file\n"); return;}
		
	for (;!feof(fsrc) ;) {
		if (fgets(bf,BFSZ-10,fsrc)==NULL) break;
		fprintf(fdst,"%s",bf);
	}
	fclose(fdst); fclose(fsrc);
}
void chan_setstate(int c, int v)
{
	/* if (c==cto) here="cto"; 	
	imsg("[%s] Set State %s ",here,mode_str[v]);*/
	chan_state[c] = v;
}
char *getque_mid(char *s)
{
	static char bf[BFSZ];
	if (strlen(s)==0) return "NOMSGID";
	ncpy(bf,s,BFSZ-1);
	s = strtok(bf," \t");
	if (s==NULL) {emsg("No message id ? \n"); return "NOMSGID";}
	return s;
}
char *getque_group(char *s)
{
	static char bf[BFSZ];
	ncpy(bf,s,BFSZ-1);
	s = strtok(bf," \t");
	if (s!=NULL) s = strtok(NULL,": \t");
	if (s==NULL) {emsg("No group ? \n"); return "NOMSGID";}
	return s;
}
char *getque_num(char *s)
{
	static char bf[BFSZ];
	ncpy(bf,s,BFSZ-1);
	s = strtok(bf," \t");
	if (s!=NULL) s = strtok(NULL,": \t");
	if (s!=NULL) s = strtok(NULL,": \t");
	if (s==NULL) {emsg("No number id ? \n"); return "NOMSGID";}
	return s;
}
void send_report(void)
{
	double g;
	int delay = lib_gmtime() - tstart;
	if (delay<1) delay = 1; 
	g = ((double) nsent)/(delay);
	imsg("%d read, %d sent, %d rejected, %g per second \n",nread,nsent,nrejected,g);
	dmsg("nget %d nsendque %d ncheck %d nsending %d, ready %d \n",nget,nsendque,nchecks,nsending,to_ready);
	log_flush();
}
void dnews_exit(char *reason, char *file, int line)
{
	send_exit();
}
void send_exit(void)
{
	send_report();
	imsg("Exiting\n");
	log_flush();
#ifdef WIN32
	svc_exit();
#endif
	exit(1);
}
void send_die(void)
{
	emsg("Xmit dieing\n");
	log_flush();
	exit(1);
}
void c_print(int chan,char * arg_list, ...)
{
 	va_list arg_ptr;
 	char *format, output[1000];
 	va_start(arg_ptr, arg_list);
 	format = arg_list;
	vsprintf(output, format, arg_ptr);
	nntp_write_s(chan,output,strlen(output));
	dmsg("nntpcmd [%d] %s ",chan,output); 
}
char *one_line(char *s)
{
	static char *next;
	char *ss;
	
	if (s==NULL) s = next;
	ss=s;
 	s = strchr(s,'\012');
	if (s==NULL) return NULL;
	*s = 0;
	next = s+1;
	if (s>ss) if (*(s-1)=='\015') *(s-1) = 0;
	return ss;
}
void uucp_send(String *item)
{
	char *bf;
	char *out;
	char *s;
	int len=0;

	bf = mymalloc(str_len(item)+100);
	out = bf;
 	s = str_s(item);
	s = one_line(s);
	for (;s!=NULL;) {
		if (strcmp(s,"..")==0) strcpy(s,".");
		sprintf(out,"%s%s",s,myeol);
		out += strlen(out);
		len += strlen(s) + 1;
		s = one_line(NULL);
	}
	uucp_write(bf,len);
	myfree(bf);
}
#define UUCP_SIZE 2000000
static FILE *fout;
static int uucp_size;
static char uucp_curname[BFSZ];
static char uucp_newname[BFSZ];
void uucp_write(char *bf, int len)
{
	int n;
	char *fname = uucp_curname;
	if (uucp_size>UUCP_SIZE) uucp_close();
 	if (fout==NULL) {
		n = uucp_next();
	 	sprintf(uucp_curname,"%s/NEWS%04d.NEW",uudir,n);
	 	sprintf(uucp_newname,"%s/NEWS%04d.BAG",uudir,n);
		fout = fopen(fname,"wb");
		if (fout==NULL) {
			filemsg("Can't open %s \n",fname);
			send_exit();
		}
	}
	fprintf(fout,"#! rnews %d%s",len,myeol);
	fwrite(bf,1,strlen(bf),fout);
	uucp_size += len;
}
void uucp_close(void)
{
 	if (!isuucp) return;
	if (fout!=NULL) fclose(fout);
	rename(uucp_curname,uucp_newname);
	fout = NULL;
	uucp_size = 0;
}
int uucp_next(void)
{
	char fname[BFSZ];
	char bf[BFSZ];
	int lcount=0;
 	FILE *f;

 	sprintf(fname,"%s/count.dat",uudir);
        f = fopen(fname,"r");
        if (f!=NULL) {
                fgets(bf,199,f);
                lcount = atoi(bf);
                fclose(f);
        }
        remove(fname);
        f = fopen(fname,"w");
        if (f!=NULL) {
                fprintf(f,"%d\n",lcount+1);
                fclose(f);
        }
 	return lcount;
}
