/* Copyright (C) 1995 NetWin World, New Zealand, All rights reserved. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef NeXT
#include <sys/time.h>
#endif 
#include "group.h"
#include "item.h"
#include "dnews.h"
#include "nntp.h"
#include "hist.h"
#include "suck.h"
#include "chan.h"
#include "sary.h"
#include "str.h"
#include "lib.h"
#include "feed.h"
#include "expire.h"
#include "ncmd.h"

void stats_cmd(char *s);
void stats_end(char *s);
char *stats_show(void);

int post_article(int chan,String *anitem);
int ncmd_gettake(int chan,String *anitem);
int ncmd_check(int chan, char *in);
int ncmd_takethis(int chan, char *in);
static char *chan_take[MAX_CHAN];
int do_ncmd(int chan, char *in)
{
	char *s;
	char cmd[100];
	static int done_init;

	done_init = TRUE;
	ncpy(cmd,get_word(in,0),99);
	strlwr(cmd);
	if (strlen(cmd)==0) return TRUE;
	stats_cmd(cmd);

	if (strstr(cmd,"ihave")==NULL)
		imsg("cmd: Do cmdx[%d:%s] {%s} {%s} \n",chan,chan_getipname(chan),in,cmd);

	if (strcmp(cmd,"group")==0) ncmd_group(chan,in);
	else if (strcmp(cmd,"list")==0) ncmd_list(chan,in);
	else if (strcmp(cmd,"listgroup")==0) ncmd_listgroup(chan,in);
	else if (strcmp(cmd,"xhdr")==0) ncmd_xhdr(chan,in);
	else if (strcmp(cmd,"article")==0) ncmd_article(chan,in);
	else if (strcmp(cmd,"fpassword")==0) ncmd_password(chan,in);
	else if (strcmp(cmd,"fcmd")==0) ncmd_cmd(chan,in);
	else if (strcmp(cmd,"flushsite")==0) ncmd_flushsite(chan,in);
	else if (strcmp(cmd,"body")==0) ncmd_body(chan,in);
	else if (strcmp(cmd,"check")==0) ncmd_check(chan,in);
	else if (strcmp(cmd,"head")==0) ncmd_head(chan,in);
	else if (strcmp(cmd,"help")==0) ncmd_help(chan,in);
	else if (strcmp(cmd,"ihave")==0) ncmd_ihave(chan,in);
	else if (strcmp(cmd,"last")==0) ncmd_last(chan,in);
	else if (strcmp(cmd,"newgroups")==0) ncmd_newgroups(chan,in);
	else if (strcmp(cmd,"newnews")==0) ncmd_newnews(chan,in);
	else if (strcmp(cmd,"next")==0) ncmd_next(chan,in);
	else if (strcmp(cmd,"post")==0) ncmd_post(chan,in);
	else if (strcmp(cmd,"quit")==0) ncmd_quit(chan,in);
	else if (strcmp(cmd,"slave")==0) ncmd_slave(chan,in);
	else if (strcmp(cmd,"stat")==0) ncmd_stat(chan,in);
	else if (strcmp(cmd,"takethis")==0) ncmd_takethis(chan,in);
	else if (strcmp(cmd,"mode")==0) ncmd_mode(chan,in);
	else if (strcmp(cmd,"authinfo")==0) ncmd_authinfo(chan,in);
	else if (strcmp(cmd,"date")==0) ncmd_date(chan,in);
	else if (strcmp(cmd,"xover")==0) ncmd_xover(chan,in);
	else if (strcmp(cmd,"xpat")==0) ncmd_xpat(chan,in);
	else if (strcmp(cmd,"xpath")==0) ncmd_xpath(chan,in);
	else if (strcmp(cmd,"xgtitle")==0) ncmd_xgtitle(chan,in);
 	else {
		chan_write_s(chan,"500 command not recognized\015\012");
	}
	dmsg("cmd: Done cmdx \n");
	stats_end(cmd);

	return TRUE;
}
/*        (n = estimated number of articles in group,
           f = first article number in the group,
           l = last article number in the group,
           s = name of the group.)
*/  
int ncmd_group(int chan, char *in)
{
	int g;
	int first,last,num;
	char grp[100];
	char bf[200];

	ncpy(grp,get_word(in,1),99);
	strlwr(grp);
	g = group_find(grp);
	imsg("cmd: News Group {%s} %d \n",grp,g);
	if (g<0 || (!chan_canreadgrp(chan,grp))) {
		chan_setgrp(chan,-1); /* no longer a selected group */		
		if (g<0) chan_write_s(chan,"411 no such news group\015\012"); 
		else  chan_write_s(chan,"411 no permission to read that group\015\012"); 
		return FALSE;
	}
	if (group_isdeleted(g)) {
		chan_write_s(chan,"411 group did exist but has been deleted\015\012"); 
		return FALSE;
	}

	first = group_getstart(g);
	last = group_getend(g);
	if (group_iscached(g)) {
#define NCMD_FAST
#ifdef NCMD_FAST
		num = last-first+1;
#else
		num = item_findnum(g,&first,&last);
#endif
	} else {
		num = (last-first+1);
		imsg("Group not current cached %s \n",grp);
	}
	sprintf(bf,"211 %d %d %d %s selected\015\012",num,first,last,grp);
	chan_write_s(chan,bf);
	chan_setgrp(chan,g);
	chan_setnum(chan,first);
	return TRUE;
}

static int skip_used;
/* should check for ID as well */
int ncmd_article(int chan, char *in)
{
	String *head,*body;
	char *mid;
	int grp,num;
	char bf[BFSZ],p1[BFSZ];
	
	if (!ncmd_findart(chan,in,&grp,&num)) return FALSE;
	
	if (!skip_used) group_used(chan,grp,num);  /* Log use of article by that user */
	head = item_head(grp,num);
	body = item_body(grp,num);
	if (head==NULL) {
		chan_write_s(chan,"423 Bad article number\015\012");
		return FALSE;
	}
	if (body==NULL) {
		emsg("fatal: Body of article missing, very strange %d %d \n",grp,num);
		chan_write_s(chan,"423 Bad article number\015\012");
		return FALSE;
	}
	
	mid = get_ahead(head,"Message-ID:");
	if (strlen(mid)>=(BFSZ-100)) emsg("Way too big message ID %d:%d\n",grp,num);
	mid[BFSZ-100] = 0;
	sprintf(bf,"220 %d %s article retrieved - head and body follows\015\012",num,mid);
	chan_write_s(chan,bf);

	dmsg("ncmd: Article Sending head\n");
	chan_write(chan,str_s(head),str_len(head));
	chan_write_s(chan,"\015\012");
	dmsg("ncmd: Article Sending body\n");
	chan_write(chan,str_s(body),str_len(body));
	chan_write_s(chan,".\015\012");
	dmsg("ncmd: Article Done\n");
	return TRUE;
}

/* xhdr header_name n1[-[n2]] | messageid*/
int ncmd_xhdr(int chan, char *in)
{
	char hdr[BFSZ],range[BFSZ];
	int from,to,i,grp;
	char *s;
	char bf[BFSZ];
	
	dmsg("ncmd: xhdr starts \n");
	ncpy(hdr,get_word(in,1),299);
	ncpy(range,get_word(in,2),299);

	if (chan_getgrp(chan)<0) {
		chan_write_s(chan,"412 no newsgroup has been selected\015\012");
		return FALSE;
	}

	if (!ncmd_range(chan,range,&grp,&from,&to)) return FALSE;

	sprintf(bf,"221 %s fields follow\015\012",hdr);
	chan_write_s(chan,bf);


	dmsg("ncmd: xhdr step2 \n");
	for  (i=from; i<=to; i++) { /* Should build into larger packet */
		s = item_getahead(grp,i,hdr);
		if (s!=NULL) {
		  if (strlen(s)==0) s = "(none)";
		  if (strlen(s)>BFSZ-20) {
		    s[BFSZ-20] = 0;
		    emsg("ncmd: Header truncated %s %d {%s} \n",group_name(grp),i,s);
		  }
			sprintf(bf,"%d %s\015\012",i,s);
			chan_write_buff(chan,bf);
		}
	}
	chan_write_buff(chan,".\015\012");
	chan_write_flush(chan);
	dmsg("xhdr: Done {%s} \n",hdr);
	return TRUE;
}
int ncmd_listgroup(int chan, char *in)
{
	char hdr[300],range[300],bf[300];
	int from,to,i,grp;
	char *s;
	char gname[300];
	
	ncpy(gname,get_word(in,1),299);
	strlwr(gname);
	grp = group_find(gname);
	if (grp<0) grp = chan_getgrp(chan);
	if (grp<0) {
		chan_write_s(chan,"412 no such newsgroup\015\012");
		return FALSE;
	}
	chan_setgrp(chan,grp);

	from = group_getstart(grp);
	to = group_getend(grp);

	sprintf(bf,"211 Article list follows\015\012"); chan_write_s(chan,bf);

	for  (i=from; i<=to; i++) { /* Should build into larger packet */
		s = item_getahead(grp,i,hdr);
		if (s!=NULL) {
			sprintf(bf,"%d\015\012",i);
			chan_write_buff(chan,bf);
		}
	}
	chan_write_buff(chan,".\015\012");
	chan_write_flush(chan);	
	return TRUE;
}
/* Enable management commands */
static int mchan; /* Manager Channel */
int ncmd_password(int chan, char *in)
{
	if (strstr(in,"balloon")!=NULL) {
		chan_write_s(chan,"481 Password accepted (obsolete) \015\012");
		mchan = chan;
	} else {
		chan_write_s(chan,"481 Password failed (obsolete) \015\012");
		mchan = -1;
	}
	return TRUE;
}

/* 
Management commands like:
	Update groups
	update a group
	flush files
	reload config files
*/
extern char debug_str[];
int ncmd_cmd(int chan, char *in)
{	
	int g,i;
	char pass[200],bf[300],bf2[300];
	char cmd[200],p1[100],p2[100],p3[100];
	dmsg("ncmd: Manager command {%s} \n",in);
	strlwr(in);
	ncpy(cmd,get_word(in,2),99) ;	
	ncpy(pass,get_word(in,1),99) ;	
	if (strcmp(pass,conf_getpass())!=0) {
		chan_write_s(chan,"481 Cannot access tellnews.pass, check dnews.conf and file protections \015\012");
		emsg("ncmd: Password failure {%s} {%s} \n",pass,conf_getpass());
		return TRUE;
	} 		
	ncpy(p1,get_word(in,3),99) ;	
	ncpy(p2,get_word(in,4),99) ;	
	ncpy(p3,get_word(in,5),99) ;	

	dnews_getkey(bf);
	if (strlen(bf)!=0) {
		sprintf(bf2,"483 %s\015\012",bf); chan_write_s(chan,bf2);
		chan_write_s(chan,"483 Please read LICENSE.TXT for registration information\015\012");
	}

	if (strcmp(cmd,"update")==0) {
		if (!suck_update_groups()) {
			chan_write_s(chan,"481 Already busy, sorry!\015\012");
			return TRUE;
		}
	} else if (strcmp(cmd,"getold")==0) {
	  if (strlen(p2)==0) strcpy(p2,"10");
	  for (i=0; i<group_n(); i++) {
	    if (group_match(p1,group_name(i))) {
	      group_setfeedend(i,group_feedend(i)-atoi(p2));
	    }
	  }
	} else if (strcmp(cmd,"getnames")==0) {
		if (!suck_update_names()) {
			chan_write_s(chan,"481 Already busy, sorry!\015\012");
			return TRUE;
		}
	} else if (strcmp(cmd,"reload")==0) {
		emsg("Reload dnews.conf\n");
		config_load();
		emsg("Reload newsfeeds.conf\n");
		feed_reload();
		emsg("Reload access.conf\n");
		access_read();
		emsg("Reinit debug mode\n");
		debug_init();
		emsg("Reload Done\n");
		chan_write_s(chan,"281 dnews.conf, newsfeeds.conf, access.conf reloaded \015\012");
	} else if (strcmp(cmd,"exit")==0) {
		emsg("ncmd: Exit requested, flushing files\n");
		dnews_ex("Exit command");
		exit(1);
	} else if (strcmp(cmd,"cacheall")==0) {
		group_cacheall(atoi(p1));
#ifdef MEMDEBUG
	} else if (strcmp(cmd,"memdebug_test")==0) {
		mem_check();
	} else if (strcmp(cmd,"memdebug_die")==0) {
		mem_kill();
#endif
	} else if (strcmp(cmd,"killitem")==0) {
		strlwr(p1);
		item_delete(group_find(p1),atoi(p2));
	} else if (strcmp(cmd,"rebuild")==0) {
		hist_rebuild();
	} else if (strcmp(cmd,"clear_cache")==0) {
		item_clearcache();
	} else if (strcmp(cmd,"crash_now")==0) {
		emsg("Crash test\n");
		dmsg("fatal: Crash test\n");
		strcpy(NULL,"crash test");
	} else if (strcmp(cmd,"expire")==0) {
		if (!suck_expire()) {
			chan_write_s(chan,"481 Already busy, sorry!\015\012");
			return TRUE;
		}
	} else if (strcmp(cmd,"debug")==0) {
		strcpy(debug_str,p1);
	} else if (strcmp(cmd,"cached")==0)  {
		if (strlen(p3)==0) strcpy(p3,"100");
		sprintf(bf,"Setting {%s} to cached %s days \015\012",p1,p2); chan_write_s(chan,bf);
		for (i=0; i<group_n(); i++) {
		  if (group_match(p1,group_name(i))) {
			group_setlife(i,atoi(p2));
			group_setcached(i,TRUE);
		  }
		}
	} else if (strcmp(cmd,"uncached")==0)  {
		sprintf(bf,"Setting {%s} to uncached \015\012",p1); chan_write_s(chan,bf);
		for (i=0; i<group_n(); i++) {
		  if (group_match(p1,group_name(i))) {
			group_setlife(i,0);
			group_setcached(i,FALSE);
		  }
		}
	} else if (strcmp(cmd,"rmgroup")==0)  {
		sprintf(bf,"Setting {%s} to deleted state \015\012",p1); chan_write_s(chan,bf);
		for (i=0; i<group_n(); i++) {
		  if (group_match(p1,group_name(i))) {
			wmsg("rmgroup %s \n",group_name(i));
			sprintf(bf,"Group Marked for delete {%s} \015\012",group_name(i)); chan_write_s(chan,bf);
			group_setlife(i,0);
			group_setcached(i,FALSE);
			group_delete(i);
		  }
		}
	} else if (strcmp(cmd,"addlost")==0)  {
		suck_addlost();
	} else if (strcmp(cmd,"purgelost")==0)  {
		suck_purgelost();
	} else if (strcmp(cmd,"stats")==0)  {
		chan_write_s(chan,"\015\012");
		chan_write_s(chan,stats_show());
		chan_write_s(chan,".\015\012");
	} else if (strcmp(cmd,"setused")==0)  {
		diskuse_set(atoi(p1)*10000);
		sprintf(bf,"281 Setting diskuse to %d MB, wait 2 minutes for the change to take effect\015\012",atoi(p1)); chan_write_s(chan,bf);
	} else if (strcmp(cmd,"matchfeed")==0)  {
		for (i=0; i<group_n(); i++) {
		  if (!group_wanted(group_name(i))) {
			wmsg("matchfeed rmgroup %s \n",group_name(i));
			sprintf(bf,"Group Marked for delete {%s} \015\012",group_name(i)); chan_write_s(chan,bf);
			group_setlife(i,0);
			group_setcached(i,FALSE);
			group_delete(i);
		  }
		}
	} else if (strcmp(cmd,"killgroup")==0)  {
		sprintf(bf,"Setting {%s} to deleted state \015\012",p1); chan_write_s(chan,bf);
		for (i=0; i<group_n(); i++) {
		  if (group_match(p1,group_name(i))) {
			group_setlife(i,0);
			group_setcached(i,FALSE);
			group_delete(i);
			if (group_remove(i)) { /* Remove it if it's empty */
				sprintf(bf,"Group Marked for delete {%s} \015\012",group_name(i)); chan_write_s(chan,bf);
				wmsg("Marked for delete %s \n",group_name(i));
			} else {
				sprintf(bf,"Group deleted {%s} \015\012",group_name(i)); chan_write_s(chan,bf);
				wmsg("Group actually deleted %s \n",group_name(i));
			}
		  }
		}
	} else if (strcmp(cmd,"resetall")==0)  {
		for (i=0; i<group_n(); i++) {
			group_setlife(i,10000);
			group_setcached(i,TRUE);
			group_setstart(i,group_getend(i)+1);
			group_setfeedend(i,0);
		}
	} else if (strcmp(cmd,"newgroup")==0)  {
		sprintf(bf,"New group {%s}, Modflag {%s}, Creator {%s}  Description {%s} \015\012",p1,p2,p3,get_rest(in,6));
		chan_write_s(chan,bf);
		group_new(p1,p3,p2,get_rest(in,6));
	} else if (strcmp(cmd,"status")==0)  {
		chan_write_s(chan,"\015\012");
		chan_write_s(chan,dnews_status());
		chan_write_s(chan,".\015\012");
	} else if (strcmp(cmd,"report")==0)  {
		dnews_manager_report("Manager report");
	} else if (strcmp(cmd,"reque")==0)  {
		feed_doit();
	} else if (strcmp(cmd,"key")==0)  {
		if (dnews_setkey(p1)) {
			chan_write_s(chan,"282 License key loaded, Thankyou\015\012");
		} else {
			chan_write_s(chan,"582 License key failed, sorry\015\012");
		}
	} else if (strcmp(cmd,"help")==0)  {
		chan_write_s(chan,"100 Legal manager commands\015\012");
		chan_write_s(chan,"cached wild days	Set a group to cached  \015\012");
		chan_write_s(chan,"uncached wild 	Set a group to uncached  \015\012");
		chan_write_s(chan,"expire			Run the expire process\015\012");
		chan_write_s(chan,"purgelost	Delete items that are lost from the history database (locks system for 5 minutes)\015\012");
		chan_write_s(chan,"status			Show what server is doing \015\012");
		chan_write_s(chan,"setused nnn		Set Megabytes used \015\012");
		chan_write_s(chan,"reque 		Requeue builtin feeds \015\012");
		chan_write_s(chan,"exit			Closedown the daemon cleanly\015\012");
		chan_write_s(chan,"getnames		Get the active.names from our feeder \015\012");
		chan_write_s(chan,"killitem grpname num	Delete a specific item\015\012");
		chan_write_s(chan,"reload		Reload news.conf ...\015\012");
		chan_write_s(chan,"update		Start a suck update\015\012");
		chan_write_s(chan,"getold wildgrp days  Get older news from the feeder\015\012");
		chan_write_s(chan,"rmgroup wild.group.name	Mark group as deleted\015\012");
		chan_write_s(chan,"matchfeed 	Delete groups that don't matchthe ME feed group list\015\012");
		chan_write_s(chan,"killgroup wild.group.name	Actually delete group and contents\015\012");
		chan_write_s(chan,"newgroup name y|m creator description	Add a new group\015\012");
		chan_write_s(chan,"register 	Create register.txt \015\012");
		chan_write_s(chan,"key nnnnn 	Enter your registration key\015\012");
		chan_write_s(chan,".\015\012");
	} else {
		chan_write_s(chan,"581 Unknown Manager command, try HELP\015\012");
		return FALSE;
	}
	chan_write_s(chan,"281 Manager command processed OK\015\012");
	return TRUE;
}
int ncmd_range(int chan, char *range, int *grp, int *from, int *to)
{
	String *head,*body;
	char *s;
	char bf[300],p1[300];
	int first,last;
	int isrange=FALSE;

	if (*range == '<') {
		hist_find(p1,grp,from);
		*to = *from;
	} else {
		*grp = chan_getgrp(chan);
		if (*grp<0) {
			chan_write_s(chan,"412 no newsgroup has been selected\015\012");
			return FALSE;
		}
		first = group_getstart(*grp);
		last = group_getend(*grp);
		s = strchr(range,'-');
		if (s!=NULL) isrange=TRUE;

		if (strlen(range)==0) { /* Default to current article */
			*from = chan_getnum(chan);
			*to = *from;
			return TRUE;
		}

		if (!isrange) {
re1:			*from = atoi(range);
			*to = *from;
			return TRUE;
		}

		s = strtok(range,"-");
		if (s==NULL) *from = first;
		else *from = atoi(s);

		s = strtok(NULL,"-");
		if (s==NULL) *to = last;
		else *to = atoi(s);
		if (*to>last) *to = last;
		if (*from<first) *from = first;
	}
	return TRUE;
}
int ncmd_findart(int chan, char *in, int *grp, int *num)
{
	String *head,*body;
	char bf[300],p1[300];
	char *s;
	
	skip_used = FALSE;
	ncpy(p1,get_word(in,1),299);
	if (strcmp(p1,"")==0) { sprintf(p1,"%d",chan_getnum(chan));}
	if (*p1 == '<') {
		if (!hist_find(p1,grp,num)) {
			chan_write_s(chan,"430 no such article found\015\012");
			return FALSE;
		}
		dmsg("ncmd: findart %d %d \n",*grp,*num);		
		return TRUE;
	} else {
		s = strchr(p1,':');
		if (s!=NULL) {
			*s = 0; s++;
			*grp = group_find(p1);
			*num = atoi(s);
			skip_used = TRUE;
			if (*grp<0) {
				chan_write_s(chan,"430 no such group found\015\012");
				return FALSE;
			}
			return TRUE;		
		}
		*grp = chan_getgrp(chan);
		if (*grp<0) {
			chan_write_s(chan,"412 no newsgroup has been selected\015\012");
			return FALSE;
		}
		*num = atoi(p1);
		if (*num<0) {
			chan_write_s(chan,"420 no current article has been selected\015\012");
			chan_setnum(chan,*num);
			return FALSE;
		}
		chan_setnum(chan,*num);
	}	
	return TRUE;
}
int ncmd_body(int chan, char *in)
{
	String *head,*body;
	int grp,num;
	char *mid;
	char bf[BFSZ];	
	if (!ncmd_findart(chan,in,&grp,&num)) return FALSE;

	group_used(chan,grp,num);  /* Log use of article by that user */
	head = item_head(grp,num);
	body = item_body(grp,num);
	if (head==NULL || body==NULL) {
		chan_write_s(chan,"423 Bad article number\015\012");
		return FALSE;
	}
	
	mid = get_ahead(head,"Message-ID:");
	sprintf(bf,"222 %d %s article retrieved - body follows\015\012",num,mid);
	chan_write_s(chan,bf);

	chan_write(chan,str_s(body),str_len(body));
	chan_write_s(chan,".\015\012");
	return TRUE;
}
int ncmd_head(int chan, char *in)
{
	String *head,*body;
	int grp,num;
	char *mid,bf[BFSZ];	
	if (!ncmd_findart(chan,in,&grp,&num)) return FALSE;

	head = item_head(grp,num);
	if (head==NULL) {
		chan_write_s(chan,"423 Bad article number\015\012");
		return FALSE;
	}
	
	mid = get_ahead(head,"Message-ID:");
	sprintf(bf,"221 %d %s article retrieved - head follows\015\012",num,mid);
	chan_write_s(chan,bf);

	chan_write(chan,str_s(head),str_len(head));
	chan_write_s(chan,".\015\012");
	return TRUE;
}
int ncmd_flushsite(int chan, char *in)
{
	char bf[300],p1[300];
	ncpy(p1,get_word(in,1),299);
	if (feed_flushsite(p1)) {
		chan_write_s(chan,"280 Site flushed\015\012");
	} else {
		chan_write_s(chan,"480 Site doest exist or couldn't flush it\015\012");
	}
	return TRUE;
}
int ncmd_help(int chan, char *in)
{
	chan_write_s(chan,"100 Legal commands\015\012");
	chan_write_s(chan,"  authinfo user Name|pass Password\015\012");
	chan_write_s(chan,"  article [MessageID|Number]\015\012");
	chan_write_s(chan,"  body [MessageID|Number]\015\012");
	chan_write_s(chan,"  check MessageID\015\012");
	chan_write_s(chan,"  date\015\012");
	chan_write_s(chan,"  group newsgroup\015\012");
	chan_write_s(chan,"  head [MessageID|Number]\015\012");
	chan_write_s(chan,"  help\015\012");
	chan_write_s(chan,"  ihave\015\012");
	chan_write_s(chan,"  last\015\012");
	chan_write_s(chan,"  list [active|active.times|newsgroups]\015\012");
	chan_write_s(chan,"  listgroup newsgroup\015\012");
	chan_write_s(chan,"  mode stream\015\012");   
	chan_write_s(chan,"  mode reader\015\012");   
	chan_write_s(chan,"  newgroups yymmdd hhmmss [GMT] [<distributions>]\015\012");
	chan_write_s(chan,"  newnews newsgroups yymmdd hhmmss [GMT] [<distributions>]\015\012");
	chan_write_s(chan,"  next\015\012");
	chan_write_s(chan,"  post\015\012");
	chan_write_s(chan,"  slave\015\012");
	chan_write_s(chan,"  stat [MessageID|Number]\015\012");
	chan_write_s(chan,"  takethis MessageID \015\012");
	chan_write_s(chan,"  xgtitle [group_pattern]\015\012");
	chan_write_s(chan,"  xhdr header [range|MessageID]\015\012");
	chan_write_s(chan,"  xover [range]\015\012");
	chan_write_s(chan,"  xpat header range|MessageID pat [morepat...]\015\012");
/*	chan_write_s(chan,"  xpath xpath MessageID\015\012");  Meaningless always */
	chan_write_s(chan,".\015\012");
	return TRUE;
}
int ncmd_check(int chan, char *in)
{
	char bf[300],p1[300];

	ncpy(p1,get_word(in,1),299);
	msg_from(chan_getipname(chan));
	msg_id(p1);
	if (!chan_canihave(chan)) {
		chan_write_s(chan,"480 transfer permission denied\015\012");
		return TRUE;
	}
	if (!lib_checkspace()) {
		sprintf(bf,"431 %s try again later (disk full) \015\012",p1);
		chan_write_s(chan,bf);
		return TRUE;
	}
	if ((hist_gotit(p1)) || (*p1!='<') ) { 
		sprintf(bf,"438 %s already have it\015\012",p1);
		chan_write_s(chan,bf);
		msg_reject("Article in history file");
	} else {
		sprintf(bf,"238 %s not here, please send it to me\015\012",p1);
		chan_write_s(chan,bf);
	}
	return TRUE;
}
int ncmd_takethis(int chan, char *in)
{
	char bf[BFSZ],p1[300];

	ncpy(p1,get_word(in,1),299);
	chan_take[chan] = mystrdup(p1);
	chan_setstate(chan,C_IHAVE);
	return TRUE;
}
#define iffree(x) if ((x)!=NULL) {free(x); x=NULL;}
int ncmd_gettake(int chan,String *anitem)
{
	char bf[BFSZ];
	char *p1 = chan_take[chan];	

	msg_from(chan_getipname(chan));
	msg_id(p1);
	chan_setstate(chan,C_CMD);
	if (feed_byposting()) imsg("ncmd: incoming IHAVE with a POSTING outward feed is a really bad idea\n");
	if (!chan_canihave(chan)) {
		chan_write_s(chan,"480 transfer permission denied\015\012");
		msg_reject("Transfer permission denied");
		iffree(chan_take[chan]);
		return TRUE;
	}
	if (!item_suck(anitem)) {
		if (item_tryagain()) {
		  sprintf(bf,"431 %s transfer failed - try again later\015\012",p1);
		  chan_write_s(chan,bf);
		} else {
		  sprintf(bf,"439 %s transfer failed - do not try again later\015\012",p1);
		  chan_write_s(chan,bf);
		}
	} else {
		sprintf(bf,"239 %s article transferred ok\015\012",p1);
		chan_write_s(chan,bf);
	}
	chan_gotpost(chan);
	iffree(chan_take[chan]);
	return TRUE;
}
int ncmd_ihave(int chan, char *in)
{
	char bf[300],p1[300];

	if (feed_byposting()) imsg("ncmd: ihave with a POSTING outward feed is a really bad idea\n");
	if (!chan_canihave(chan)) {
		chan_write_s(chan,"480 transfer permission denied\015\012");
		return TRUE;
	}
	if (!lib_checkspace()) {
		chan_write_s(chan,"436 sorry disk full - try again later\015\012");
		return TRUE;
	}
	ncpy(p1,get_word(in,1),299);
	msg_from(chan_getipname(chan));
	msg_id(p1);
	chan_setstate(chan,C_IHAVE);
	chan_write_s(chan,"335 send article to be transferred.  End with <CR-LF>.<CR-LF>\015\012");
	hist_gotit(p1); /* Will cache answer for when item is actually recieved*/
 
 /*	if ((hist_gotit(p1)) || (*p1!='<') ) { 
		chan_write_s(chan,"435 article not wanted - do not send it\015\012");
		msg_reject("Article in history file or not valid");
	} else {
	}
*/
	return TRUE;
}

int ncmd_getihave(int chan, char *in, int sz)
{
	char *end;
	int rlen;
	static String *anitem[MAX_CHAN];

	msg_from(chan_getipname(chan));
	if (anitem[chan]==NULL) anitem[chan] = str_new();
	if (strcmp(in,".\015\012")==0) {
		if (chan_take[chan]!=NULL) {
			ncmd_gettake(chan,anitem[chan]); 
			str_free(anitem[chan]); anitem[chan] = NULL;
			return TRUE;
		}
		if (!item_suck(anitem[chan])) {
			if (item_tryagain()) {
				chan_write_s(chan,"436 transfer failed - try again later\015\012");
			} else {
				chan_write_s(chan,"437 article rejected - do not try again\015\012");
			}
		} else {
			chan_write_s(chan,"235 article transferred ok\015\012");
		} 
		chan_setstate(chan,C_CMD);
		chan_gotpost(chan);
		str_free(anitem[chan]); anitem[chan] = NULL;
	} else {
		str_adds(anitem[chan],in,sz);
	}
	return TRUE;
}
int ncmd_getcur(int chan, char *in, int *grp, int *num)
{
	*grp = chan_getgrp(chan);
	if (*grp<0) {
		chan_write_s(chan,"412 no newsgroup has been selected\015\012");
		return FALSE;
	}
	*num = atoi(get_word(in,1));
	if (strlen(get_word(in,1))==0) *num = chan_getnum(chan);
	if (*num<0) {
		chan_write_s(chan,"420 no current article has been selected\015\012");
		return FALSE;
	}
	return TRUE;
}
int ncmd_last(int chan, char *in)
{
	String *head=NULL;
	char bf[BFSZ];
	int grp,num,start;
	char *mid;

	/* Search backwards for valid article number, only return status info. */
	if (!ncmd_getcur(chan,in,&grp,&num)) return FALSE;
	start = group_getstart(grp);
	for (;num>start;) {
		num--;
		head = item_head(grp,num);
		if (head!=NULL) break;
	}
	if (head==NULL) {
		chan_write_s(chan,"422 no previous article in this group\015\012");
		return FALSE;		
	}
	chan_setnum(chan,num);
	mid = get_ahead(head,"Message-ID:");
	sprintf(bf,"221 %d %s article retrieved - request text separately\015\012",num,mid);
	chan_write_s(chan,bf);
	return TRUE;
}
int ncmd_list(int chan, char *in)  /* Should cache active list for 1-10 minutes for speed*/
{
	int i;
	Sary *flist;
	char bf[BFSZ];
	char p2[BFSZ];

	ncpy(bf,get_word(in,1),BFSZ-1); strlwr(bf);
	ncpy(p2,get_word(in,2),BFSZ-1);
	strlwr(p2);
	if ( (strcmp(bf,"active")==0) && (strlen(p2)>0) ) {
		i = group_find(p2);
		if (i<0) {
			chan_write_s(chan,"503 newsgroup not found\015\012");
			return TRUE;
		} 
		sprintf(bf,"215 list active %s\015\012",p2); chan_write_s(chan,bf);
		chan_write_s(chan,group_getlist(i));
	} else if ( (strcmp(bf,"active")==0) || (strcmp(bf,"")==0) ) {
		chan_write_s(chan,"215 list of newsgroups follows\015\012");
		for (i=0; i<group_n(); i++) {
			chan_write_s(chan,group_getlist(i));
		}
	} else if (strcmp(bf,"active.times")==0) {
		group_times_flush();
		flist = s_alloc();
		s_read(flist,DNEWS_ACTIVE_TIMES);
		if (s_len(flist)==0) goto not_available;
		chan_write_s(chan,"215 list of active.times follows\015\012");
		for (i=0; i<s_len(flist); i++) {
			sprintf(bf,"%s\015\012",s_line(flist,i));
			chan_write_s(chan,bf);
		}
		s_free(flist);
	} else if (strcmp(bf,"newsgroups")==0) {
		if (group_names_n()==0) goto not_available;
		for (i=0; i<group_names_n(); i++) {
			sprintf(bf,"%s\015\012",group_names_get(i));
			chan_write_s(chan,bf);
		}
	} else {
		chan_write_s(chan,"501 command syntax error (or un-implemented option)\015\012");
		return TRUE;
	}
	chan_write_s(chan,".\015\012");
	return TRUE;
not_available:
	chan_write_s(chan,"503 That list is not available at this site, sorry.\015\012");
	return TRUE;
}
int gmt_to_local(int t) /* s = yymmddhhmmss */
{
#ifdef VMS  /* Need to implement this properly*/
	return t;
#else
#ifdef USEGMTOFF
	time_t tt;
	struct tm *m; 
	tt = time(NULL);
	m = localtime(&tt);
	return t - m->tm_gmtoff;
#else
	tzset();
	return t-timezone;
#endif
#endif 
}
int nntp_time(char *s) /* s = yymmddhhmmss */
{
	char syear[10],smonth[10],sday[10],shour[10],sminute[10],ssecond[10];
	int year;
	struct tm t;

	memset(&t,0,sizeof(t));
	ncpy(syear,s,2);
	ncpy(smonth,s+2,2);
	ncpy(sday,s+4,2);
	ncpy(shour,s+6,2);
	ncpy(sminute,s+8,2);
	ncpy(ssecond,s+10,2);

	t.tm_year = atoi(syear);
	t.tm_mon = atoi(smonth)-1;
	t.tm_mday = atoi(sday);
	t.tm_hour = atoi(shour);
	t.tm_min = atoi(sminute);
	t.tm_sec = atoi(ssecond);
#ifdef VAX
#define mktime mymktime
#endif
#ifdef VMS
	return lib_local_to_gmt(mktime(&t));
#else
	return lib_fixtime(mktime(&t));
#endif
}
int ncmd_newgroups(int chan, char *in)  /* newgroups date time GMT <dist> */
{
	Sary *groupt; 
	char dtime[BFSZ],dist[BFSZ],bf[BFSZ],gname[BFSZ],gdate[BFSZ];
	int gmt = FALSE;
	int g;
	int i,date;
	char *s;
	
	ncpy(dtime,get_word(in,1),100);
	strcat(dtime,get_word(in,2));
	if (strcmp("GMT",get_word(in,3))==0) gmt = TRUE;
	if (gmt) ncpy(dist,get_word(in,4),500);
	else ncpy(dist,get_word(in,3),500);

	date = nntp_time(dtime);		
	if (gmt) date = lib_gmt_to_local(date); /* correction done twice!! */
	
	groupt = s_alloc();
	group_times_flush();
	s_read(groupt,DNEWS_ACTIVE_TIMES);

	chan_write_s(chan,"231 list of new newsgroups follows\015\012");

	for (i=0; i<s_len(groupt); i++) {
		ncpy(bf,s_line(groupt,i),BFSZ-1);
		s = strtok(bf," \t\n");
		if (s!=NULL) ncpy(gname,s,BFSZ-1);
		strlwr(gname);
		s = strtok(NULL," \t\n");
		if (s!=NULL) ncpy(gdate,s,BFSZ-1);
		if (atoi(gdate)>date) {
		  g = group_find(gname);
		  if (g>=0) { 
		   if ( (dist[0]==0) || dist_match(dist,gname)) {
			chan_write_s(chan,group_getlist(g));
		   }	
		  }
		}
	}
	s_free(groupt);
	chan_write_s(chan,".\015\012");
	return TRUE;
}
int dist_match(char *dist, char *group)
{
	char bf[BFSZ];
	char *s;
	if (strlen(dist)==0) return TRUE;
	strcpy(bf,group);
	s = strchr(bf,'.');
	if (s!=NULL) *s = 0;
	s = strchr(dist,'>');
	if (s!=NULL) *s = ',';
	strcat(bf,",");
	if (strstr(dist,group)!=NULL) return TRUE;
	return FALSE;
}
int dist_matchlist(char *dist, char *glist)
{
	char bf[BFSZ];
	char *gl;
	int r;
	char *s;

	gl = mystrdup(glist);
	if (gl==NULL) { emsg("Fatal: Memory allocation failure in matchlist\n"); return FALSE;} 
	s = strtok(gl,",");
	for (;s!=NULL;s++) {
		if (dist_match(dist,s)) { r=TRUE; goto doreturn;} 
		s = strtok(NULL,",");
	}
	r = FALSE;
doreturn:;
	free(gl);
	return r;
}
/* newnews newsgroups,...  yymmdd hhmmss ["GMT"] [<distributions>] */
int ncmd_newnews(int chan, char *in)
{
	char dtime[BFSZ],dist[BFSZ],bf[BFSZ],wildgrps[BFSZ];
	int gmt = FALSE;
	int date;
	char *h;
	char *s;
	int i;
	
	ncpy(wildgrps,get_word(in,1),500);
	ncpy(dtime,get_word(in,2),100);
	strcat(dtime,get_word(in,3));
	if (strcmp("GMT",get_word(in,4))==0) gmt = TRUE;
	if (gmt) ncpy(dist,get_word(in,5),500);
	else ncpy(dist,get_word(in,4),500);

	date = nntp_time(dtime);		
	if (gmt) date = lib_gmt_to_local(date); /* correction done twice!! */
dmsg("ncmd: nntp_time %d ",date);	

	/* <msgid> \t date-a~date-e~date-c \t comp.os.vms/1104 alt.x/12  */
	h = hist_after(date);  /* Finds first non expired item after 'date' */
	for (; h!=NULL; ) {
		  s = hist_groupfirst(h);
		  for (i=0;s!=NULL;i++) {
			if (group_match(wildgrps,s)) {	
			  ncmd_newnews2(chan, dist, group_find(s),hist_num(h,0));
			}
			s = hist_groupnext();
		  }
		h = hist_next();			
	}
	chan_write_s(chan,".\015\012");
	return TRUE;
}				
int ncmd_newnews2(int chan,char *dist, int g, int num)
{	
	/* get groups   header and check it matches one of the distributions*/
	/* Then get DISTRIBUTION: header and check it also matches */
	int i;
	String *head=NULL;
	char *s;
	char bf[BFSZ],ds[BFSZ];

	head = item_head(g,num);
	if (head==NULL) return FALSE;
	
	if (dist_matchlist(dist,get_ahead(head,"Newsgroups:"))) {
		ncpy(ds,get_ahead(head,"Distribution:"),400);
		if ((ds[0]==0) || (dist_matchlist(dist,ds))) {
			s = get_ahead(head,"Message-ID:");
			if (s!=NULL) {
				sprintf(bf,"%s\015\012",s);
				chan_write_s(chan,bf);
				return TRUE;
			}
		}
		return FALSE;
	}
	return FALSE;
}

int ncmd_next(int chan, char *in)
{
	String *head=NULL;
	char bf[BFSZ];
	int grp,num,last;
	char *mid;

	/* Search forwards for valid article number, only return status info. */
	if (!ncmd_getcur(chan,in,&grp,&num)) return FALSE;
	last = group_getend(grp);
dmsg("ncmd: next curnum %d last %d \n",num,last);
	for (;num<last;) {
		num++;
		head = item_head(grp,num);
		if (head!=NULL) break;
	}
	if (head==NULL) {
		chan_write_s(chan,"421 no next article in this group\015\012");
		return FALSE;		
	}
	chan_setnum(chan,num);
	mid = get_ahead(head,"Message-ID:");
	sprintf(bf,"223 %d %s article retrieved - request text separately\015\012",num,mid);
	chan_write_s(chan,bf);
	return TRUE;
}
int ncmd_post(int chan, char *in)
{
	char bf[300],p1[300];

	if (!chan_canpost(chan)) {
		chan_write_s(chan,"440 posting not allowed\015\012");
	} else {
		chan_setstate(chan,C_POST);
		chan_write_s(chan,"340 send article to be posted. End with <CR-LF>.<CR-LF>\015\012");
	}
	return TRUE;
}
int ncmd_getpost(int chan, char *in, int sz)
{
	char *end;
	int rlen;
	static String *anitem[MAX_CHAN];

	if (anitem[chan]==NULL) anitem[chan] = str_new();

	if (strcmp(in,".\015\012")==0) {
		chan_setstate(chan,C_CMD);
		post_article(chan,anitem[chan]);
		feed_flush();
		str_copys(anitem[chan],"",0);
		/* Trigger a reload of that group */
		if (chan_getgrp(chan)>=0) suck_queue(group_name(chan_getgrp(chan)),1);
		str_free(anitem[chan]);  anitem[chan] = NULL;
	} else {
		str_adds(anitem[chan],in,sz);
	}
	return TRUE;
}
int ncmd_quit(int chan, char *in)
{
	chan_write_s(chan,"205 closing connection - goodbye!\015\012");
	chan_close(chan); /* Might need to set a state to allow time to close*/
	return TRUE;
}
int ncmd_slave(int chan, char *in)
{
	chan_write_s(chan,"202 slave status noted\015\012");
	return TRUE;
}
int ncmd_stat(int chan, char *in)
{
	String *head,*body;
	int grp,num;
	char *mid,bf[BFSZ];	
	if (!ncmd_findart(chan,in,&grp,&num)) return FALSE;

	head = item_head(grp,num);
	if (head==NULL) {
		chan_write_s(chan,"423 Bad article number\015\012");
		return FALSE;
	}
	
	mid = get_ahead(head,"Message-ID:");
	sprintf(bf,"223 %d %s article retrieved - request text separately\015\012",num,mid);
	chan_write_s(chan,bf);

	return TRUE;
}
int ncmd_mode(int chan, char *in)
{
	
	char p1[BFSZ];
	ncpy(p1,get_word(in,1),200);
	strlwr(p1);

	if (strcmp(p1,"reader")==0) {
		chan_write_s(chan,"200 DNews can do anything - posting ok.\015\012");
	} else if (strcmp(p1,"stream")==0) {
		chan_write_s(chan,"203 StreamOK\015\012");
	} else {
		chan_write_s(chan,"500 Syntax error or bad command\015\012");
	}
	return TRUE;
}
/* authinfo user Name  */
/* authinfo pass Password */
int ncmd_authinfo(int chan, char *in)
{
	char p1[BFSZ];
	ncpy(p1,get_word(in,1),200);
	strlwr(p1);
	if (strcmp(p1,"user")==0) {
		chan_write_s(chan,"381 PASS required\015\012");
		chan_setuser(chan,get_word(in,2));
	} else if (strcmp(p1,"pass")==0) {
		if (strcmp(chan_getuser(chan),"")==0) {
			chan_write_s(chan,"482 USER required\015\012");
		}
		if (chan_check(chan,get_word(in,2))) {
			chan_write_s(chan,"281 Ok\015\012");
		} else {
			chan_write_s(chan,"502 Authentication error\015\012");
		}
	} else {
		chan_write_s(chan,"481 bad authinfo param\015\012");
	}
	return TRUE;
}
int ncmd_date(int chan, char *in)
{
	time_t t;
	struct tm *gmt;
	char bf[200];
	time(&t);
	gmt = gmtime(&t); 

	/* should really correct for timezone, but this insn't a valid command
	/* anyway */
	/* VMS might return NULL, so this should make it work anyway */
	if (gmt==NULL) 	gmt = localtime(&t);  

	sprintf(bf,"111 %04.4d%02.2d%02.2d%02.2d%02.2d%02.2d\015\012",
		gmt->tm_year+1900, gmt->tm_mon + 1, gmt->tm_mday,
		gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
	chan_write_s(chan,bf);
	return TRUE;
}
/* Let's fake this at the moment, I think my system is fast enough */
/* xover [range] 						   */
int ncmd_xover(int chan, char *in)
{
	char hdr[300],range[300];
	int from,to,i,grp;
	char *s;
	
	ncpy(range,get_word(in,1),299);
	if (!ncmd_range(chan,range,&grp,&from,&to)) return FALSE;
	chan_write_s(chan,"224 data follows\015\012");
	dmsg("ncmd: Range %d-%d \n",from,to);
	for  (i=from; i<=to; i++) { /* Should build into larger packet maybe */
		s = item_xover(grp,i);
		if (s!=NULL) {
			chan_write_s(chan,s);
		}
	}
	
	chan_write_s(chan,".\015\012");
	return TRUE;
}
int ncmd_xpath(int chan, char *in)
{
	chan_write_s(chan,"500 xpath is meaningless in DNEWS, see docs\015\012");
	return TRUE;
}

/* xpat header range|MessageID pattern which can have spaces */
/* Same as xhdr, but must also match pattern to be printed, pattern
   can contain wildcards and spaces.  Heaven knows what this is used
   for.
*/
int ncmd_xpat(int chan, char *in)
{
	char hdr[300],range[300],wildpat[BFSZ];
	int from,to,i,grp;
	char *s;
	char bf[300];
	
	ncpy(wildpat,get_rest(in,3),299);
	ncpy(hdr,get_word(in,1),299);
	ncpy(range,get_word(in,2),299);
	
	if (!ncmd_range(chan,range,&grp,&from,&to)) return FALSE;
	sprintf(bf,"221 %s matches follow\015\012",hdr);
	chan_write_s(chan,bf);

	for  (i=from; i<=to; i++) { 
		s = item_getahead(grp,i,hdr);
		if (s!=NULL) {
			if (match_wild(wildpat,s)) {	
				sprintf(bf,"%d %s\015\012",i,s);
				/* Build into larger packet */
				chan_write_buff(chan,bf);
			}
		}
	}
	chan_write_buff(chan,".\015\012");
	chan_write_flush(chan);	
	return TRUE;
}

/* xgtitle [group_pattern] 
   Used by ANU-news reader (as if anyone uses that, huh! fnews is 10 times
   better 
*/
int ncmd_xgtitle(int chan, char *in)
{
	char bf[BFSZ],wildpat[BFSZ],gname[BFSZ],gdate[BFSZ];
	int i;

	ncpy(wildpat,get_word(in,1),300);

	chan_write_s(chan,"282 list follows\015\012");
	for (i=0; i<group_names_n(); i++) {
		ncpy(gname,group_names_get(i),BFSZ-1);
		if (match_wild(wildpat,get_word(gname,0))) {
			sprintf(bf,"%s\015\012",gname);
			chan_write_s(chan,bf);
		}
	}
	chan_write_s(chan,".\015\012");
	return TRUE;
}
char *dnews_status(void)
{
	char bf[BFSZ];
	static String *stat;
	if (stat==NULL) stat = str_new();
	lib_checkspace();

	str_copysn(stat,"");
	str_addsn(stat,feed_status());
	str_addsn(stat,chan_status());
	str_addsn(stat,expire_status());
	
	str_addsn(stat,suck_status());
	str_addsn(stat,hist_status());
	str_addsn(stat,lib_status());
	return str_s(stat);
}
#define TEMP_FILE file_work("report.tmp")
void dnews_manager_report(char *subj)
{
	String *xnew;
	char bf[300],to[300];

	ncpy(bf,config_get(C_REPORT),100);
	strlwr(bf); if (strstr(bf,"no")!=NULL) return;

	ncpy(to,config_get(C_MANAGER),100);

	xnew = str_new();
	sprintf(bf,"From: %s@%s\015\012",DNEWS_NAME,config_get(C_MYNAME)); str_copysn(xnew,bf);
	sprintf(bf,"Subject: %s\015\012",subj); str_addsn(xnew,bf);
	sprintf(bf,"To: %s\015\012",to); str_addsn(xnew,bf);
	str_addsn(xnew,"\015\012");
	
	str_addsn(xnew,dnews_status());
	remove(TEMP_FILE);
	if (!str_write(xnew,TEMP_FILE)) filemsg("Error writing %s \n",TEMP_FILE); 
	if (!lib_mail_file(to,subj,TEMP_FILE)) {
		emsg("dnews: Failed to email report to manager\n");
	}
	str_free(xnew);
}


static char *stat_names[100];
static int nstat,stat_cur,stat_n[100];
static double stat_start,stat_estart,stat_time[100],stat_etime[100];
void stats_cmd(char *s)
{
	int i;
 	for (i=0; i<nstat; i++) if (strcmp(s,stat_names[i])==0) goto gotit;
	if (nstat>50) {emsg("stat problem %s \n",s); return;}
	stat_names[nstat++] = mystrdup(s);
	i = nstat-1;
gotit:
	stat_cur = i;
	stat_start = clock();
	stat_estart = lib_gmtime();
}
void stats_end(char *s)
{
 	stat_etime[stat_cur]  += (lib_gmtime()-stat_estart);
 	stat_time[stat_cur]  += (clock()-stat_start);
 	stat_n[stat_cur]++;
}
char *stats_show(void)
{
	char bf[BFSZ];
	static String *stat;
	int i;
	int cps=CLOCKS_PER_SEC;
	if (stat==NULL) stat = str_new();

	str_copysn(stat,"");
	for (i=0; i<nstat; i++) {
		if (stat_n[i]>0) {
		  sprintf(bf,"%s  cpu=%g  calls=%d   cpuavg=%g elapsed=%g\015\012",stat_names[i]
				,stat_time[i]/cps,stat_n[i]
				,(stat_time[i]/cps)/stat_n[i]
				,(stat_etime[i])/stat_n[i]);
		  str_addsn(stat,bf);
		}
	}
	return str_s(stat);
}
