/* Copyright (C) 1995 NetWin World, New Zealand, All rights reserved. */
#define ISNCMD 1
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include <errno.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 "gstat.h"
#include "lib.h"
#include "db.h"
#include "feed.h"
#include "expire.h"
#include "ncmd.h"
#include "ftslib.h"
#include "header.h"
#include "pull.h"
#include "ml.h"
#include "lib_spawn.h"
#ifdef WIN32
#define popen _popen
#define pclose _pclose
#endif
#ifdef KEYLIB
#include "keylib.h"
#endif

int db_reindex(void);
void ncmd_hdr_finish(int chan, int sendhead);

void db_set_rebuild(int t);
int test_strings(void);
int suck_reindex(void);
int ncmd_xdfind(int chan, char *in);
int ncmd_xdauthent(int chan, char *in);
void ncmd_body_finish(int chan);
int hdr_article(int chan, int grp, int num);
int hdr_body(int chan, int grp, int num);
void ncmd_list_finish(int chan);
void ncmd_list_names_finish(int chan);
int ncmd_xdarticle(int chan, char *in);
int suck_findlost(void);
void slave_xdmulti(int chan, char *in);
void slave_xdrealip(int chan, char *in);
void slave_xdrealip2(int chan, char *in);
int authent_group(int c, int g);
int auth_deny(int chan);

#undef mymalloc
#undef myfree
#define mymalloc(x) mymalloc2(x,M_NCMD)
#define myfree(x) myfree2(x,M_NCMD)
int nntp_set_poison(char *ip);

void cmd_complete(int chan, int p1, int p2, int from, int to, int state);
void ncmd_body_finish(int chan);
void stats_cmd(char *s);
void control_check(int chan, char *fname);
void check_groups(String *xitem);
void suck_start_convert(char *path);
void db_correct_used(void);
void remove_tree(int chan);
void db_trim_range(void);
int ncmd_xreplic(int chan, char *in);
void cmd_test_malloc(int chan, int mb);
void check_world(int chan);
int ncmd_login(int chan, char *in);
int ncmd_tomail(int chan, char *in);


void spam_crc_age(void);
void spam_from_age(void);
int pgp_verify(String *xitem, char *issuer);
int ncmd_xdisdnews(int chan, char *in);
#define iffree(x) if ((x)!=NULL) {myfree(x); x=NULL;}
#define ifstrfree(x) if ((x)!=NULL) {mystrfree(x,SD_NCMD); x=NULL;}



String *stats_show(void);
int ncmd_dsearch(int chan, char *in);
#ifdef WIN32
        void sleep(int x);
#endif

static char *art_fail;
int invalid_mid(char *mid, char *reason);
String *stats_show_in(void);
void slave_xdpost(int schan, char *in);
int delay_finish(int chan);
static int ismid;
int ncmd_pipe_write(char *bf);

String *stats_groups_read(char *s, int chan);
void ncmd_reload(void);

void cmd_showfile(int chan, char *fname);
void cmd_emsg_tail(int chan, char *lines);
void slave_newgroup(int xchan, char *in);
int ncmd_xpat_new(int chan, char *in);

int ncmd_iswronghost(int chan);

int db_check_index(void);
int post_article(int chan,String *anitem, char *s);
int ncmd_gettake(int chan,String *anitem, int toobig);
int ncmd_check(int chan, char *in);
void ncmd_feed_refeed(int chan, char *feedname);
int ncmd_takethis(int chan, char *in);
static char *chan_take[MAX_CHAN];
static char chan_ismid[MAX_CHAN];
static int chan_nsucked[MAX_CHAN];
static int chan_postchan[MAX_CHAN];
static char chan_postslave[MAX_CHAN];
static int chan_authgrp[MAX_CHAN]; /* The group this user is allowed to read without checking */
static int chan_donedeny[MAX_CHAN]; /* The group this user is allowed to read without checking */
static int chan_authcnt[MAX_CHAN]; /* The group this user is allowed to read without checking */
static int chan_wait_for_dot[MAX_CHAN];
static char *chan_wronghost[MAX_CHAN];

#ifdef WIN32
#define REALKEY TRUE
#endif
#ifdef NETWARE
#define REALKEY TRUE
#endif
#define REALKEY TRUE
#ifdef REALKEY
extern int key_expired;
#else
int key_expired;
#endif
static int speed_test,xspeed_test;
static int busy_from[MAX_CHAN];
static char *busy_xpat[MAX_CHAN];
static char *busy_hdr[MAX_CHAN];
static String *busy_str1[MAX_CHAN];
static char *busy_search[MAX_CHAN];
static int busy_senddot[MAX_CHAN];
static int busy_to[MAX_CHAN];
static int busy_p1[MAX_CHAN];
static int busy_p2[MAX_CHAN];
static int cmd_mode[MAX_CHAN];
#define DNEWS_SUBSCRIPTIONS file_work("subscrip.dat")
char *busy_names[]={"XOVER ","XHDR ","BODY ","LIST ","LIST_NAMES","NEWGROUPS","NEWNEWS","FEED","HDR_BODY","HDR_ARTICLE","DELAY","GROUP_SUCK","THREAD","","",""};
int ihavein_clear(int i);
int fts_reload(void);
static int ispause;
static char pause_reason[BFSZ];
#define XBFSZ 16000
static int fail_mid;
int ncmd_xdtakethis(int chan, char *in);
void copy_group(int chan,char *gfrom,char *gto, int newmid);
static int skip_used;
int pop_free(int chan);
int pop_quelookup(int chan, char *user, char *pass);
int pop_init(void);
void key_extend(void);
int chan_nchan(void);
int pass_fail_recent(char *s);
int pass_fail_add(char *s);
int seq_next(int c, int cmd, int hasargs);
int seq_init(int c);
static char find_mid[BFSZ];

enum {S_MODE,S_GROUP,S_STAT,S_ARTICLE,S_NEXT,S_XOVER,S_XHDR,S_BODY,S_HEAD,S_OTHER,S_END};

typedef struct {int cmd; int rep; int hasargs;} Slist;

static Slist slist1[]={
	S_MODE,1,TRUE
	,S_GROUP,1,TRUE
	,S_STAT,0,TRUE  /* Any number of these */
	,S_ARTICLE,0,FALSE
	,S_NEXT,1,FALSE
	,S_ARTICLE,1,FALSE
	,S_NEXT,1,FALSE
	,S_ARTICLE,1,FALSE
	,S_NEXT,1,FALSE	
	,S_END,0,0
};

static Slist slist2[]={
	S_MODE,1,TRUE
	,S_GROUP,1,TRUE
	,S_STAT,0,TRUE  /* Any number of these */
	,S_ARTICLE,10,TRUE
	,S_NEXT,10,TRUE
	,S_ARTICLE,10,TRUE
	,S_NEXT,10,TRUE
	,S_ARTICLE,10,TRUE
	,S_NEXT,10,TRUE
	,S_END,0,0
};

static int ihavein_failed[MAX_CHAN];

void ncmd_set_mode(int chan, int x)
{
	cmd_mode[chan] = x;
}
void ncmd_hdr_send(int id)
{
        int i;
        for (i=0; i<chan_nchan(); i++) {
                if (!nntp_getbusy(i)) continue;
                if (cmd_mode[i]==M_HDR_BODY) 
                        ncmd_hdr_finish(i,FALSE);       
                if (cmd_mode[i]==M_HDR_ARTICLE) 
                        ncmd_hdr_finish(i,TRUE);        
        }
}
char *chan_busystr(int c)
{
        static char bf[100];
	if (nntp_getbusy(c)) if (cmd_mode[c]<0) return "Cleared";
        if (nntp_getbusy(c)) return busy_names[cmd_mode[c]];
        return "";
}

int do_ncmd(int chan, char *in)
{
        char cmd[700];
	int islocalhost =  FALSE;
        char bf[BFSZ];
	static char tmp[20000];
        int ispass = FALSE;
        char xin[BFSZ];
        static int done_init;
        int start = lib_gmtime();


        if (!done_init) {
                pop_init();
		remove(SPAM_BRIEF);
        }
	if (chan_wait_for_dot[chan]) {
		if (strcmp(in,".")==0) {
			chan_wait_for_dot[chan] = FALSE;
		}
		return TRUE;
	}

        if (key_expired) key_extend() ;
	chan_restoreipnum(chan);

        done_init = TRUE;
        ncpy(cmd,get_word(in,0),99);
        ncpy(xin,in,100);
        strlwr(cmd);

	if (config_true(C_LOG_EVERYTHING)) {
                imsg("cmd: Do cmdx[%d:%.100s] {%.100s}\n",chan,chan_getipname(chan),xin);
	}

        if (strlen(cmd)==0) return TRUE;
        /*stats_cmd(cmd);*/



        if (strcmp(cmd,"xreplic")==0) {
          if (config_true(C_LOG_IHAVE)) {
                ncpy(bf,in,80);
                imsg("%.90s (%d total)\n",bf,strlen(in));
          }
          ncmd_xreplic(chan,in); 
          goto donecmd;
        }
        if (strcmp(cmd,"authinfo")==0) ispass = TRUE;

        if (strcmp(cmd,"fcmd")==0) ispass = TRUE;
        if (strcmp(cmd,"xdauthent")==0) ispass = TRUE;
        if (strcmp(cmd,"xdrealip")==0) ispass = TRUE;
        if (strcmp(cmd,"xdrealip2")==0) ispass = TRUE;
        if (strcmp(cmd,"xdslave")==0) ispass = TRUE;
        if (strcmp(cmd,"newnews")==0) {
                ncpy(bf,in,80);
                imsg("cmd: Do cmdx[%d:%.100s] {%.100s}\n",chan,chan_getipname(chan),bf);
                ncmd_newnews(chan,in);
                goto donecmd;
        }
        if (strlen(in)>540) {
                ncpy(bf,in,199);
                emsg("Got garbage from chan[%d]{%.100s}\n",chan,bf);
		strlwr(bf);
		if (strncmp(bf,"check ",6)==0) {
	                lprintf(tmp,20000-1,"438 %.3000s article rejected - command too long\015\012",get_word_long(in,1));
	                chan_write_s(chan,tmp);
		} else if (strncmp(bf,"ihave ",6)==0) {
	                chan_write_s(chan,"435 article not wanted - command too long\015\012");
		} else if (strncmp(bf,"takethis ",9)==0) {
	                lprintf(tmp,20000-1,"439 %.3000s article not wanted - command too long\015\012",get_word_long(in,1));
	                chan_write_s(chan,tmp);
			/* wait for dot */
			chan_wait_for_dot[chan] = TRUE;
		} else {
	                chan_write_s(chan,"500 command too long\015\012");
		}
                goto donecmd;
        }

        if (config_true(C_LOG_NNTP)) {
          if (config_true(C_LOG_IHAVE)) {
                if (!ispass) imsg("cmd: Do cmdx[%d:%s] {%s} {%s} \n",chan,chan_getipname(chan),xin,cmd);
          } else {
           if (strstr(cmd,"ihave")==NULL)
           if (strstr(cmd,"takethis")==NULL)
           if (strstr(cmd,"xactive")==NULL)
           if (strstr(cmd,"check")==NULL)
                if (!ispass) imsg("cmd: Do cmdx[%d:%s] {%s} {%s} \n",chan,chan_getipname(chan),xin,cmd);
          }
        } 
        if (strstr(cmd,"authinfo")!=NULL) {
                strcpy(xin,"authinfo XXXXX");
        }
        if (strcmp(cmd,"fcmd")==0) {
                strcpy(xin,"fcmd XXXXX");
        }
        if (strcmp(cmd,"xdauthent")==0) {
                strcpy(xin,"xdauthent XXXXX");
        }
        if (!ispass) dmsg("cmd: %s\n",xin);
        
        if (chan_needauth(chan)) {
		if (strcmp(chan_getipnum(chan),"127.0.0.1")==0) islocalhost = TRUE;
		if (islocalhost && (strcmp(cmd,"xhlookup")==0)  ) {
			 slave_xhlookup(chan,in);
		} else if (islocalhost && (strcmp(cmd,"xhresult")==0)  ) {
			 slave_xhresult(chan,in);
		} else if (strcmp(cmd,"authinfo")==0) ncmd_authinfo(chan,in);
                else if (strcmp(cmd,"fcmd")==0) ncmd_cmd(chan,in);
                else if (strcmp(cmd,"xdslave")==0) slave_xcmd(chan,in);
                else if (strcmp(cmd,"xdisdnews")==0) ncmd_xdisdnews(chan,in);
	        else if (strcmp(cmd,"xdomain")==0) ncmd_xdomain(chan,in);
                else if (strcmp(cmd,"xdrealip")==0) slave_xdrealip(chan,in);
                else if (strcmp(cmd,"xdrealip2")==0) slave_xdrealip2(chan,in);
	        else if (strcmp(cmd,"mode")==0) ncmd_mode(chan,in);
	        else if (strcmp(cmd,"fcmd_login")==0) ncmd_login(chan,in);
                else if (strcmp(cmd,"xdauthent")==0) ncmd_xdauthent(chan,in);
	        else if (strcmp(cmd,"xdmulti")==0) {if (islocalhost) slave_xdmulti(chan,in);}
                else if (strcmp(cmd,"quit")==0) ncmd_quit(chan,in);
                else {
			imsg("User authentication required, ignoring command (%.20s)\n",cmd);
                        lprintf(bf,BFSZ-1,"480 User and password still required, authinfo command (%.20s)\015\012",cmd);
			if (strlen(config_get(C_AUTH_MESSAGE))>0) {
                         lprintf(bf,BFSZ-1,"480 %s\015\012",config_get(C_AUTH_MESSAGE));
			}
                        chan_write_s(chan,bf);
                        chan_authcnt[chan] = 0;

                }
                goto donecmd;
        }
        vtrace();
	trace_addmsg(xin);
	chan_restoreipnum(chan);
        if (strcmp(cmd,"authinfo")==0) ncmd_authinfo(chan,in);
        else if (strcmp(cmd,"group")==0) ncmd_group(chan,in);
        else if (strcmp(cmd,"check")==0) ncmd_check(chan,in);
        else if (strcmp(cmd,"list")==0) ncmd_list(chan,in);
        else if (strcmp(cmd,"fcmd_login")==0) ncmd_login(chan,in);
        else if (strcmp(cmd,"xdauthent")==0) ncmd_xdauthent(chan,in);
        else if (strcmp(cmd,"xdsearch")==0) ncmd_dsearch(chan,in);
        else if (strcmp(cmd,"xdfind")==0) ncmd_xdfind(chan,in);
        else if (strcmp(cmd,"dlist")==0) ncmd_dlist(chan,in);
        else if (strcmp(cmd,"xdlist")==0) ncmd_dlist(chan,in);
        else if (strcmp(cmd,"xdomain")==0) ncmd_xdomain(chan,in);
	else if (strcmp(cmd,"xtomail")==0) ncmd_tomail(chan,in);
        else if (strcmp(cmd,"xdisdnews")==0) ncmd_xdisdnews(chan,in);
	else if (strcmp(cmd,"listgroup")==0) ncmd_listgroup(chan,in);
	else if (strcmp(cmd,"listbug")==0) ncmd_listbug(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,"xdarticle")==0) ncmd_xdarticle(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,"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,"xdtakethis")==0) ncmd_xdtakethis(chan,in);
        else if (strcmp(cmd,"mode")==0) ncmd_mode(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 if (strcmp(cmd,"xactive")==0) slave_xactive(chan,in);
        else if (strcmp(cmd,"xhlookup")==0) slave_xhlookup(chan,in);
        else if (strcmp(cmd,"xhresult")==0) slave_xhresult(chan,in);
        else if (strcmp(cmd,"xdslave")==0) slave_xcmd(chan,in);
        else if (strcmp(cmd,"xdpost")==0) slave_xdpost(chan,in);
        else if (strcmp(cmd,"xdmulti")==0) slave_xdmulti(chan,in);
        else if (strcmp(cmd,"xdrealip")==0) slave_xdrealip(chan,in);
        else if (strcmp(cmd,"xdrealip2")==0) slave_xdrealip2(chan,in);
        else if (atoi(cmd)>0) imsg("Ignoring status when expecting command \n");
        else {
		seq_next(chan,S_OTHER,FALSE);
                chan_write_s(chan,"500 command not recognized\015\012");
                /* if (strlen(cmd)<30) */ dmsg("ncmd: Unknown command (%.60s) \n",cmd);
        }
donecmd:
        vtrace();
        dmsg("cmd: DONE %s\n",xin);
        if ((lib_gmtime()-start) > 3) {
                ncpy(bf,in,80);
		strlwr(bf);
		if (strstr(bf,"fcmd")==NULL) 
		if (strstr(bf,"authinfo")==NULL) 
                  imsg("ncmd: slow cmd {%s} %d Seconds\n",bf,lib_gmtime()-start);
		else 
                  imsg("ncmd: slow cmd {authinfo} %d Seconds\n",lib_gmtime()-start);
        }

        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.)
*/  
extern int glob_ok;
void suck_isused(void);
void user_addgrp(int c, int g);
extern int key_limitgroups;

int isdnews_hash(int n)
{
	int fiddle[]={123,543,1233,4321,666,7774,2343};
	int tot=n;
	int i;
	for (i=0; i<(n%6); i++)  {
		tot *= n % fiddle[i];
		tot += fiddle[i];
		tot = tot % 10000000;
	}
	tot += n + 3256734;
	tot += 3*((n%100) * (n%100));
	tot += 4*((n%50) * (n%70) * (n%60));
	if (tot<0) tot = -tot;
	return tot;
}

int ncmd_xdisdnews(int chan, char *in)
{
	char bf[BFSZ];
	char xkey[BFSZ];
	char *k;
	int c = isdnews_hash(atoi(get_word(in,1)));
        dnews_getkey(xkey);
	if (strlen(xkey)>0) k = "Trial";
	else k = "Registered";
	if (strstr(xkey,"OK")!=NULL) k = "Registered"; 

	lprintf(bf,BFSZ-1,"200 %d %s DNEWS %s \"%s\"\r\n",c,k,DNEWS_VERSION,SYSTEM_TYPE);
	chan_write_s(chan,bf);
	return	TRUE;
}
int ncmd_tomail(int chan, char *in)
{
	String *result;
	result = str_new();
	tomail_nntp_cmd(in,chan_getuser(chan),result);
	chan_write(chan,result);
	str_free(result);
	return TRUE;
}
int ncmd_group(int chan, char *in)
{
        int g;
        int first,last,num;
        char grp[100];
        char bf[200];
        static int first_time;

	if (config_true(C_DMULTI_SLAVE_ONLY)) {
		if (!is_slave()) {
	                chan_write_s(chan,"411 Please reconnect to port 119 or 7501 etc, not 7500\015\012"); 
		        return FALSE;
		}
	}

        if (first_time==0) first_time = lib_gmtime();
	seq_next(chan,S_GROUP,TRUE);
        ncpy(grp,get_word(in,1),99);
        strlwr(grp);
        
	if (strlen(config_get(C_POISON_GROUP))>0) {
		if (group_match(config_get(C_POISON_GROUP),grp)) {
			imsg("Virus from (%s) attempted to read poison group (%s) (%s)\n",chan_getipnum(chan),grp,config_get(C_POISON_GROUP));
			nntp_set_poison(chan_getipnum(chan));
	                chan_write_s(chan,"411 Do not read that group it contains viruses, access blocked for some time as you are probably a virus robot\015\012"); 
			ncmd_quit(chan,in);
			return TRUE;
		}
	}

	g = group_find(grp);
        user_addgrp(chan,g);
        imsg("cmd: News Group {%s} %d \n",grp,g);
        if (g<0) {
         if (config_true(C_EMPTY_ONGROUP)) {
                if (empty_isempty(grp)) {
                        empty_unempty(grp);
                        g = group_find(grp);
                }
         }
        }
#ifdef KEYLIB
	{
		char reason[BFSZ];
	        if (!keylib_isok(reason)) {
	                lprintf(bf,200,"411 Key %s\r\n",reason);
	                chan_write_s(chan,bf); 
			if (is_slave()) keylib_uncache();
	                return FALSE;
		}
	}
#endif
	if (key_expired) {
                chan_write_s(chan,"411 License key expired, use tellnews extend\015\012"); 
                return FALSE;
        }
        if (g<0 ) {
                chan_setgrp(chan,-1); /* no longer a selected group */          
                if (g<0) chan_write_s(chan,"411 no such news group\015\012"); 
		imsg("group command failed %.40s, no such group locally\n",grp);
                return FALSE;
        }
        if (group_isdeleted(g)) {
                chan_write_s(chan,"411 group did exist but has been deleted\015\012"); 
		imsg("group command failed %.40s, group is deleted\n",grp);
                return FALSE;
        }
        if (key_limitgroups) if (g>1100) {
                chan_write_s(chan,"411 BBS version of dnews limited to 1000 news groups sorry\015\012");
                return FALSE;
        }
        suck_isused();
        num = group_getrange(g);
        first = group_getstart(g);
        last = group_getend(g);
        if (lib_gmtime()-first_time>60) if (g % 10 == 5) if (!glob_ok) {
                /*ncmd_tell(1);*/
/*                nntp_xxsetup(4);*/
        }
        chan_setrealuser(chan);
        chan_setgrp(chan,g);
        chan_setnum(chan,first);
        if (!chan_donedeny[chan]) {
                if (!authent_group(chan,g)) {
                        chan_donedeny[chan] = TRUE;
                        auth_deny(chan);
			imsg("User denied access to group\n");
                        return FALSE;
                }
        }
        if (config_true(C_CACHE_ONGROUP)) {
          if (!group_iscached(g)) {
                if (is_slave()) group_setcached_slave(g);
                else {
                 if (!group_inupdate(g)) {
                        suck_queue_anyone(group_name(g),0);
                 }
                 group_setupdate(g,TRUE);
                }
          }
          if (!skip_used) group_used(chan,g,1,0);  /* Log use of article by that user */
        }
        /* If not cached, and not fetched already, then do a fetch wait */
        
        if (!is_slave()) if (chan_nsucked[chan]<200) if (num<2) if (!group_iscached(g)) if (hdr_prefetch()>0) {
                if (!group_binsucked(g)) {
                        chan_nsucked[chan]++;
                        hque_suck(g,chan);
                        cmd_complete(chan,g,0,0,0,M_SUCK_GROUP);
                        return TRUE;
                }
        }
        lprintf(bf,200-1,"211 %d %d %d %s selected\015\012",num,first,last,group_name(g));
        chan_write_s(chan,bf);
        return TRUE;
}
int ncmd_group_finish(int chan)
{
        int g;
        int first,last,num;
        char bf[200];
        static int first_time;

        nntp_setbusy(chan,FALSE);
        g = busy_p1[chan];
        num = group_getrange(g);
        first = group_getstart(g);
        last = group_getend(g);
        chan_setrealuser(chan);
        chan_setgrp(chan,g);
        chan_setnum(chan,first);
        lprintf(bf,200-1,"211 %d %d %d %s selected\015\012",num,first,last,group_name(g));
        chan_write_s(chan,bf);
        return TRUE;    
}


/* should check for ID as well */
char *get_reason(void)
{
        return pause_reason;
}
void cmd_pause(int t, char *reason)
{
        ncpy(pause_reason,reason,BFSZ-1);
        ispause = t;
        if (ispause) wmsgf("Pausing incoming feeds {%s} \n",reason);
        else wmsgf("UnPausing incoming feeds {%s} \n",reason);
}
static int ispause_ihave;
void cmd_pause_ihave(int t, char *reason)
{
        ncpy(pause_reason,reason,BFSZ-1);
        ispause_ihave = t;
        if (ispause_ihave) wmsgf("Pausing incoming IHAVE feeds {%s} \n",reason);
        else wmsgf("UnPausing incoming IHAVE feeds {%s} \n",reason);
}

static int dogap;
void ncmd_set_gap(void)
{       
        dogap = TRUE;
}
void ncmd_logoff(int chan,char *reason)
{
	char bf[BFSZ];
        lprintf(bf,BFSZ-1,"502 Limit exceeded %s\015\012",reason);
        chan_write_s(chan,bf);
	chan_close(chan); 
	chan_authcnt[chan] = 0;
}
void p_compress_between(void);
static int nwrong;
int ncmd_article(int chan, char *in)
{
        String *head,*body;
        int rfrom;
        char *mid;
	int r;
	char reason[BFSZ];
        int grp,num;
	int done=FALSE;
        char bf[BFSZ];
        int chunk = atoi(config_get(C_BODY_CHUNK));
        if (chunk==0) chunk = 64000;
     
	if (ncmd_iswronghost(chan)) return FALSE;
	if (config_true(C_ARTICLE_430)) {
                chan_write_s(chan,"430 no such article found (FAKE GAP2)\015\012");
                return TRUE;
	}

	seq_next(chan,S_ARTICLE,strlen(in)>7);
        if (dogap) {
                dogap = FALSE;
                chan_write_s(chan,"423 no such article found (FAKE GAP)\015\012");
                return TRUE;
        }

        if (is_slave()) {
          ncpy(bf,get_word(in,1),BFSZ-1);
          if (bf[0]=='<') { slave_lookup(chan,bf,SL_ARTICLE); return TRUE; } 
        }

        vtrace();
        if (!ncmd_findart(chan,in,&grp,&num)) return FALSE;
        if (db_iscancel(grp,num)) {
                imsg("item cancelled %s:%d\n",group_name(grp),num);
                goto gone;
        }

	if (!config_true(C_ML_DISABLE)) {
	 r = ml_add(chan,0,0,reason);
	 if (!r) { ncmd_logoff(chan,reason); return TRUE; }
 	 if (!ncmd_recheck(chan)) {
		ncmd_logoff(chan,"User limits exceeded");
		return TRUE;
	 }

	}

        if (hdr_matchgroup(group_name(grp))) {
download:       if (!group_isdownloading(grp,num)) {
                        if (!skip_used) group_used(chan,grp,num,0);
			if (ishdr_new()) {
				body = item_body_size(grp,num,0,chunk,&rfrom);
				if (body!=NULL) if (hdr_islink(str_s(body),&grp,&num)) {
		                        hdr_article(chan,grp,num);
			                return TRUE;
				}
				return FALSE;
			}
                        hdr_article(chan,grp,num);
                        return TRUE;
                }
                imsg("Not fetching body of downloading message\n");
        }
        stat_start(X_ART_GET);
        vtrace();
        head = item_head(grp,num);
        vtrace();
        body = item_body_size(grp,num,0,chunk,&rfrom);
        stat_end(X_ART_GET);

        if (!done) if (head!=NULL) if (body!=NULL) if (hdr_islink(str_s(body),&grp,&num)) {
		done = TRUE;
		goto download;
	}

        vtrace();
        if (body!=NULL) if (!skip_used) group_used(chan,grp,num,str_len(body));  /* Log use of article by that user */
        if (head==NULL) {
gone:           chan_write_s(chan,art_fail);
                return FALSE;
        }
        if (body==NULL) {
                imsg("ncmd: Body of article missing, maybe expired {%s} %d \n",group_name(grp),num);
                chan_write_s(chan,art_fail);
                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;
        }
	if (strlen(find_mid)>0) if (strcmpnc(mid,find_mid)!=0) {
		nwrong++;
		emsg("Messageid wrong %s %d %s %s\n",group_name(grp),num,mid,find_mid);
		art_fail = "430 Wrong article found sorry\015\012";
		chan_write_s(chan,art_fail);
		return FALSE;
	}

        lprintf(bf,BFSZ-1,"220 %d %s article retrieved - head and body follows\015\012",num,mid);
	
	if (!config_true(C_ML_DISABLE)) {
	 r = ml_add(chan,1,str_len(head) + str_len(body),reason);
	 if (!r) { ncmd_logoff(chan,reason); return TRUE; }
	}
	ncmd_pipe(chan,str_len(head)+str_len(body),"article");

	if (config_true(C_AUTH_READART)) {
		if (!chan_readart(chan,group_name(grp),num,db_last_len(),reason)) {
			ncmd_logoff(chan,reason); return TRUE; 
		}
	}

	stat_start(X_ART_SEND);
        chan_write_s(chan,bf);
        chan_write(chan,head);
        chan_write_s(chan,"\015\012");
        if (rfrom<0) {
                str_addsn(body,".\015\012");
        }
        chan_write(chan,body);
        if (rfrom>=0) {
		imsg("body_todo sent %d so far\n",rfrom);
                cmd_complete(chan,grp,num,rfrom,0,M_BODY);
        } 
        stat_end(X_ART_SEND);
        return TRUE;
}
int hdr_article(int chan, int grp, int num)
{
        int h = hcache_findadd(group_getgroupid(grp),num);
	chan_ismid[chan] = ismid;
        cmd_complete(chan,h,atoi(art_fail),0,0,M_HDR_ARTICLE);
        ncmd_hdr_finish(chan,TRUE);
        return TRUE;
}
int hdr_body(int chan, int grp, int num)
{
        int h = hcache_findadd(group_getgroupid(grp),num);
	chan_ismid[chan] = ismid;
        cmd_complete(chan,h,atoi(art_fail),0,0,M_HDR_BODY);
        ncmd_hdr_finish(chan,FALSE);
        return TRUE;
}
void test_stack(void);
int ncmd_xdarticle(int chan, char *in)
{
        String *head,*body;
        int rfrom;
        char *mid,*s;
        int grp,num;
        char bf[BFSZ];
        int chunk = atoi(config_get(C_BODY_CHUNK));
        if (chunk==0) chunk = 20000;

        if (!ncmd_findart(chan,in,&grp,&num)) return FALSE;
        head = item_head(grp,num);
        body = item_body_size(grp,num,0,chunk,&rfrom);
        if (head==NULL) {
                chan_write_s(chan,art_fail);
                return FALSE;
        }
        if (body==NULL) {
                emsg("fatal: Body of article missing, very strange {%s} %d \n",group_name(grp),num);
                chan_write_s(chan,art_fail);
                return FALSE;
        }
        if (!skip_used) group_used(chan,grp,num,str_len(body));  /* Log use of article by that user */
        
        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;
        }
        lprintf(bf,BFSZ-1,"220 %d %s article retrieved - head and body follows\015\012",num,mid);
        chan_write_s(chan,bf);
        chan_write(chan,head);
        chan_write_s(chan,"\015\012");
        chan_write(chan,body);
        s = str_s(body) + str_len(body) - 1;
        if (str_len(body)>0) {
                if (*s!='\012') {
                        chan_write_s(chan,"\015\012");
                } 
        }
        chan_write_s(chan,".\015\012");
        return TRUE;
}
int g_group_read(int grp);


/* xhdr header_name n1[-[n2]] | messageid*/
int ncmd_xhdr_old(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;

        lprintf(bf,BFSZ-1,"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-21] = 0;
                    wmsg("ncmd: Header truncated %s %d {%s} \n",group_name(grp),i,s);
                  }
                        lprintf(bf,BFSZ-1,"%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 cmd_time(char *s) 
{
        /*  yyyy-mm-dd */
        int y;
        struct tm t;

        memset(&t,0,sizeof(t));
        s = strtok(s,"-:/\\,"); if (s==NULL) return -1;
        y = atoi(s);
        if (y>200) y -= 1900;
        t.tm_year = y;
        s = strtok(NULL,"-:/\\,"); if (s==NULL) return -1;
        t.tm_mon = atoi(s)-1;
        s = strtok(NULL,"-:/\\,"); if (s==NULL) return -1;
        t.tm_mday = atoi(s);
#ifdef VAX
#define mktime mymktime
#endif
#ifdef VMS
        return lib_local_to_gmt(mktime(&t));
#else
        return lib_gmt_to_local(lib_fixtime(mktime(&t)));
#endif
}


int cmd_showgroup(int chan, char *gname, char *d_from, char *d_to)
{
        char bf[BFSZ];
        String *head,*body;
        int from,to,i,grp;
        int rfrom;
        char msgid[BFSZ],xfrom[BFSZ],subject[BFSZ],xdate[BFSZ];
        double delay;
        int n;
        int ntot;
        int sz,tot,start;
        int nlost = 0;
        int tfrom,tto;
        int idate;
	int zmax;

        tfrom = cmd_time(d_from); tto = cmd_time(d_to);

        
        ntot = tot = 0;
        start = lib_gmtime();
        strlwr(gname);
        grp = group_find(gname);
        if (grp<0) {
                chan_write_s(chan,"412 no such newsgroup\015\012");
                return FALSE;
        }
        from = group_getstart(grp);
        to = group_getend(grp);
        n = g_list_start(grp);
	zmax = max_items();

        for (i=0; (i<zmax) && (n>=0); i++)  {
                if ((n>=from) && (n<=to)) {
                        head = item_head(grp,n);
                        body = item_body_size(grp,n,0,20000,&rfrom);
                        if (head!=NULL) if (body!=NULL) {
                                ncpy(msgid, get_ahead(head,"Message-ID:"),100);
                                ncpy(subject, get_ahead(head,"Subject:"),60);
                                ncpy(xfrom, get_ahead(head,"From:"),60);
                                ncpy(xdate, get_ahead(head,"Date:"),60);
                                idate = parsedate(xdate);
                                if (tfrom>idate) goto donext;
                                if (tto>0) if (tto<idate) goto donext;
                                lprintf(bf,BFSZ-1,"%d %s %s %s\r\n",n,subject,xfrom,msgid);
                                chan_write_s(chan,bf);        
                                sz = str_len(body) + str_len(head);
                                tot += sz;
                                ntot++;
                        }
                        if ((head==NULL) || (body==NULL)) {
                                show_mrec_s(subject,grp,n);
                                nlost++;
                                lprintf(bf,BFSZ-1,"%d Missing, %s",n,subject);
                                chan_write_s(chan,bf);          
                        }
                }
donext:         if (n%10==3) if ((lib_gmtime()-start)>160) break;
                n = g_list_next();
        }
        delay = lib_gmtime() - start;
        if (delay==0) delay = 1;
        lprintf(bf,BFSZ-1,"Read %d items %dk bytes, %.2f items/sec %.2f k/sec, lost %d\r\n",
                ntot,tot/1000,(double) ntot/delay,(double) tot/delay,nlost);
        chan_write_s(chan,bf);
        return TRUE;
}
int cmd_showgroup_hist(int chan, char *gname)
{
        char bf[BFSZ];
        String *head;
        int from,to,i,grp;
        char msgid[BFSZ],xfrom[BFSZ],subject[BFSZ],xdate[BFSZ];
        double delay;
        int n;
        int ntot;
        int tot,start;
        int nlost = 0;
        int zmax;
	int nmiss=0;


        
        ntot = tot = 0;
        start = lib_gmtime();
        strlwr(gname);
        grp = group_find(gname);
        if (grp<0) {
                chan_write_s(chan,"412 no such newsgroup\015\012");
                return FALSE;
        }
        from = group_getstart(grp);
        to = group_getend(grp);
        n = g_list_start(grp);
	zmax = max_items();

        for (i=0; (i<zmax) && (n>=0); i++)  {
                if ((n>=from) && (n<=to)) {
                        head = item_head(grp,n);
                        if (head!=NULL) {
                                ncpy(msgid, get_ahead(head,"Message-ID:"),100);
                                ncpy(subject, get_ahead(head,"Subject:"),60);
                                ncpy(xfrom, get_ahead(head,"From:"),60);
                                ncpy(xdate, get_ahead(head,"Date:"),60);
				if (!hist_exists(msgid)) {				
					lprintf(bf,BFSZ-1,"MISSING %d %s %s %s\r\n",n,subject,xfrom,msgid);
					chan_write_s(chan,bf);        
					nmiss++;
				}
				ntot++;
                        }
                        if ((head==NULL) ) {
                                show_mrec_s(subject,grp,n);
                                nlost++;
                                lprintf(bf,BFSZ-1,"%d Missing, %s",n,subject);
                                chan_write_s(chan,bf);          
                        }
                }
	        if (n%10==3) if ((lib_gmtime()-start)>160) break;
                n = g_list_next();
        }
        delay = lib_gmtime() - start;
        if (delay==0) delay = 1;
        lprintf(bf,BFSZ-1,"Read %d items %dk bytes, %.2f items/sec %.2f k/sec, lost %d\r\n",
                ntot,tot/1000,(double) ntot/delay,(double) tot/delay,nmiss);
        chan_write_s(chan,bf);
        return TRUE;
}
int item_test_read(int chan, char *gname)
{
        char bf[BFSZ];
        String *head,*body;
        int from,to,i,grp;
        int rfrom;
        double delay;
        int n;
        int ntot;
        int sz,tot,start;
        int nlost = 0;
	int zmax;

        ntot = tot = 0;
        start = lib_gmtime();
        strlwr(gname);
        grp = group_find(gname);
        if (grp<0) {
                chan_write_s(chan,"412 no such newsgroup\015\012");
                return FALSE;
        }
        from = group_getstart(grp);
        to = group_getend(grp);
        n = g_list_start(grp);
	zmax = max_items();

        for (i=0; (i<zmax) && (n>=0); i++)  {
                if ((n>=from) && (n<=to)) {
                        head = item_head(grp,n);
                        body = item_body_size(grp,n,0,20000,&rfrom);
                        if (head==NULL) nlost++;
                        if (body==NULL) nlost++;
                        if (head!=NULL) if (body!=NULL) {
                                sz = str_len(body) + str_len(head);
                                dmsg("db: Read %s:%d %d bytes\n",gname,n,sz);
                                tot += sz;
                                ntot++;
                        }
                }
                if (n%10==3) if ((lib_gmtime()-start)>10) break;
                n = g_list_next();
        }
        delay = lib_gmtime() - start;
        if (delay==0) delay = 1;
        lprintf(bf,BFSZ-1,"Read %d items %dk bytes, %.2f items/sec %.2f k/sec, lost %d\r\n",
                ntot,tot/1000,(double) ntot/delay,(double) tot/delay/1000.0,nlost);
        chan_write_s(chan,bf);
        return TRUE;
}
int ncmd_listbug(int chan, char *in)
{
        char bf[BFSZ];
        int from,to,i,grp;
        char gname[BFSZ];
	int zmax;
        int n;
	seq_next(chan,S_OTHER,FALSE);
        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;
        }
        if ((!chan_canreadgrp(chan,group_name(grp)))) {
                chan_setgrp(chan,-1); /* no longer a selected group */          
                chan_write_s(chan,"411 no permission to read that group\015\012"); 
                return FALSE;
        }

        chan_setgrp(chan,grp);

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

        lprintf(bf,BFSZ-1,"211 Article list follows\015\012"); chan_write_s(chan,bf);

        n = g_list_start(grp);
	zmax = max_items();
        for (i=0; i<500000; i++) {
                        lprintf(bf,BFSZ-1,"%d\015\012",n);
                        chan_write_buff(chan,bf);
	}
        chan_write_buff(chan,".\015\012");
        chan_write_flush(chan); 
        return TRUE;
}
int ncmd_listgroup(int chan, char *in)
{
        char bf[BFSZ];
        int from,to,i,grp;
        char gname[BFSZ];
	int zmax;
        int n;
	seq_next(chan,S_OTHER,FALSE);
        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;
        }
        if ((!chan_canreadgrp(chan,group_name(grp)))) {
                chan_setgrp(chan,-1); /* no longer a selected group */          
                chan_write_s(chan,"411 no permission to read that group\015\012"); 
                return FALSE;
        }

        chan_setgrp(chan,grp);

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

        lprintf(bf,BFSZ-1,"211 Article list follows\015\012"); chan_write_s(chan,bf);

        n = g_list_start(grp);
	zmax = max_items();
        for (i=0; (i<zmax) && (n>=0); i++)  {
                if ((n>=from) && (n<=to)) {
                        lprintf(bf,BFSZ-1,"%d\015\012",n);
                        chan_write_buff(chan,bf);
                } else imsg("Not showing item outside range %d\n", n);
                n = g_list_next();
	        if (i%1000==2) chan_write_flush(chan); 
        }
        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 cmd_finish(int chan)
{
        if (chan<0) return TRUE;
	if (nntp_getbusy(chan)) {
        dmsg("ncmd: Background [%d] %d\n",chan,cmd_mode[chan]);
        switch(cmd_mode[chan]) {
          case M_XOVER:
	        stat_start(X_XOVER);
                xover_finish(chan);     
	        stat_end(X_XOVER);
                break;
          case M_XHDR:
	        stat_start(X_XHDR);
                xhdr_finish(chan);      
	        stat_end(X_XHDR);
                break;
          case M_XDELAY:
                delay_finish(chan);     
                break;
          case M_BODY:
                stat_start(X_FBODY);
                ncmd_body_finish(chan);         
                stat_end(X_FBODY);
                break;
          case M_SUCK_GROUP:
                break;
          case M_HDR_BODY:
                ncmd_hdr_finish(chan,FALSE);    
                break;
          case M_HDR_ARTICLE:
                ncmd_hdr_finish(chan,TRUE);     
                break;
          case M_LIST:
                stat_start(X_FLIST);
                ncmd_list_finish(chan);         
                stat_end(X_FLIST);
                break;
          case M_NEWGROUPS:
                stat_start(X_FNEWGROUPS);
                ncmd_newgroups_finish(chan);    
                stat_end(X_FNEWGROUPS);
                break;
          case M_NEWNEWS:
                stat_start(X_FNEWNEWS);
                ncmd_newnews_finish(chan,NULL);         
                stat_end(X_FNEWNEWS);
                break;
          case M_LIST_NAMES:
                ncmd_list_names_finish(chan);   
                break;
          case M_FEED:  
                live_send_chunk(chan);
                break;
        }
        
        dmsg("ncmd: Background Done [%d] %d\n",chan,cmd_mode[chan]);
	}
        if (!nntp_getbusy(chan)) {
                if (chan_ismore(chan)) chan_dorest(chan);
        }
        return TRUE;
}
int ncmd_range(int chan, char *range, int *grp, int *from, int *to)
{
        char *s;
        int first,last;
        int isrange=FALSE;

        if (*range == '<') {
                *grp = -1;
                *to = *from;
                if (!hist_find(range,grp,from)) {
                        chan_write_s(chan,"430 item not found\015\012");
                        return FALSE;
                }
                *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) {
                  *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;
}
void set_failmid(int t)
{
        fail_mid = t;
}
int ncmd_findart(int chan, char *in, int *grp, int *num)
{
        char p1[BFSZ];
        char *s;

		ncpy(find_mid,"",10);
        vtrace();
        group_unknown(FALSE);
        *grp = chan_getgrp(chan);
	ismid = FALSE;        
        art_fail = "423 no such article number in this group\015\012"; /* Assume it's a number */ 
        if (fail_mid) { /* Slave lookup by MID */
               art_fail = "430 no such article found\015\012";
        }
        skip_used = FALSE;
        ncpy(p1,get_word(in,1),199);
        if (strcmp(p1,"")==0) { lprintf(p1,BFSZ-1,"%d",chan_getnum(chan));}
        if (*p1 == '<') {
			ncpy(find_mid,p1,BFSZ-1);
		ismid = TRUE;
                art_fail = "430 no such article found\015\012";
                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);               
                goto check_access;
        } 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,"411 no such group found\015\012");
                                return FALSE;
                        }
                        goto check_access;
                }
                *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);
        }
        goto check_access;
check_access:
	if (ismid) if (config_true(C_READ_MID_OK)) {
		return TRUE;
	}
        if (!authent_group(chan,*grp)) {
                auth_deny(chan);
                return FALSE;
        }
        return TRUE;
}
void ncmd_reload(void)
{
        vtrace();
        imsgf("Reload dnews.conf\n");
               config_load();
               imsgf("Reload newsfeeds.conf\n");
        db_reload();
               feed_reload();
               imsgf("Reload access.conf\n");
               access_read();
               imsgf("Reinit debug mode\n");
               debug_init();
               suck_load();
        lib_recheck_space();
        imsgf("Reload expire.conf\n");
        expire_pile_load();
                imsgf("Reload Done\n");
}
int ncmd_body(int chan, char *in)
{
        String *head,*body;
        int rfrom;
	int r;
	char reason[BFSZ];
        int grp,num;
        char *mid;
	int done=FALSE;
        char bf[BFSZ];  
        int chunk = atoi(config_get(C_BODY_CHUNK));
        if (chunk==0) chunk = 20000;
	if (ncmd_iswronghost(chan)) return FALSE;

        vtrace();
	seq_next(chan,S_BODY,strlen(in)>4);
        if (is_slave()) {
          ncpy(bf,get_word(in,1),BFSZ-1);
          if (bf[0]=='<') { slave_lookup(chan,bf,SL_BODY); return TRUE; } 
        }


        if (!ncmd_findart(chan,in,&grp,&num)) return FALSE;


        if (hdr_matchgroup(group_name(grp))) {
download:       if (done) return FALSE;
		done = TRUE; 
		if (!group_isdownloading(grp,num)) {
                        if (!skip_used) group_used(chan,grp,num,0);
			if (ishdr_new()) {
				body = item_body_size(grp,num,0,chunk,&rfrom);
				if (body!=NULL) if (hdr_islink(str_s(body),&grp,&num)) {
		                        hdr_body(chan,grp,num);
			                return TRUE;
				}
				return FALSE;
			}
                        hdr_body(chan,grp,num);
                        return TRUE;
                }
                imsg("Not downloading body of downloading message \n");
        }       
        head = item_head(grp,num);
        body = item_body_size(grp,num,0,chunk,&rfrom);
        if (head!=NULL) if (body!=NULL) if (hdr_islink(str_s(body),&grp,&num)) goto download;
        if (head==NULL || body==NULL) {
                chan_write_s(chan,art_fail);
                return FALSE;
        }     
		
        if (!skip_used) group_used(chan,grp,num,str_len(body));  /* Log use of article by that user */
        mid = get_ahead(head,"Message-ID:");
	if (strlen(find_mid)>0) if (strcmpnc(mid,find_mid)!=0) {
		nwrong++;
		emsg("Messageid wrong %s %d %s %s\n",group_name(grp),num,mid,find_mid);
		art_fail = "430 Wrong article found sorry\015\012";
		chan_write_s(chan,art_fail);
		return FALSE;
	}

       	if (!config_true(C_ML_DISABLE)) {
	 r = ml_add(chan,1,str_len(body),reason);
	 if (!r) {  ncmd_logoff(chan,reason); return TRUE; }
  	 if (!ncmd_recheck(chan)) {
		ncmd_logoff(chan,"User limits exceeded");
		return TRUE;
	 }

	}
	ncmd_pipe(chan,str_len(body),"body");
	if (config_true(C_AUTH_READART)) {
		if (!chan_readart(chan,group_name(grp),num,db_last_len(),reason)) {
			ncmd_logoff(chan,reason); return TRUE; 
		}
	}

	lprintf(bf,BFSZ-1,"222 %d %s article retrieved - body follows\015\012",num,mid);
        chan_write_s(chan,bf);
        if (rfrom<0) {
                str_addsn(body,".\015\012");
        }
        chan_write(chan,body);


        if (rfrom>=0) {
                cmd_complete(chan,grp,num,rfrom,0,M_BODY);
        } 
        return TRUE;
}
void ncmd_body_finish(int chan)
{
        String *body;
	char reason[BFSZ];
        int rfrom;
        int chunk = atoi(config_get(C_BODY_CHUNK));
        if (chunk==0) chunk = 20000;

        vtrace();
        body = item_body_size(busy_p1[chan],busy_p2[chan],busy_from[chan]
                ,chunk,&rfrom);
        if (body==NULL) {chan_write_s(chan,"\015\012"); goto fail;}
        busy_from[chan] = rfrom;
        if (rfrom<0) {
                str_addsn(body,".\015\012");
        }
        if (body!=NULL) if (!skip_used) group_used(chan,busy_p1[chan],busy_p2[chan],str_len(body));  /* Log use of article by that user */
	ml_add(chan,1,str_len(body),reason);
	ncmd_pipe(chan,str_len(body),"body");
        chan_write(chan,body);
        if (busy_from[chan]<0) {
                nntp_setbusy(chan,FALSE);       
        }
        return;
fail:   chan_write_s(chan,".\015\012");
        nntp_setbusy(chan,FALSE);       
}
void hdr_strip(String *xitem)
{
        char *start;
        char *s;
        int len;
        
        start = str_s(xitem);
        s = strstr(start,"\r\n\r\n");
        if (s==NULL) return;
        len = strlen(s+4);
        memmove(start,s+4,len+1);
        str_setlen(xitem,len);
}

void ncmd_hdr_finish(int chan, int sendhead)
{
        String *xitem;
	int id;
        int num;
        static int xxx;
        char bf[BFSZ];
        char *mid;
        int failid = busy_p2[chan];
	char reason[BFSZ];
        int finished=FALSE;
        int xfrom=busy_from[chan];
        int chunk = atoi(config_get(C_BODY_CHUNK));
        if (chunk==0) chunk = 20000;
        
        /*
                Possible return values
                        upto = -1  Finished
                        xitem = NULL failed
                        str_len(xitem)==0 then there is no new data yet
        */
        if (busy_from[chan]<0) return; /* Already finished this item */
        xitem = hcache_get(busy_p1[chan],xfrom,chunk,&busy_from[chan]);
	id = busy_p1[chan];
        num = hcache_num(busy_p1[chan]);
        if (busy_from[chan]<0) finished = TRUE;
if (finished) imsg("Got END of item %d\n",num);
else {
        if ((xxx++ % 200)==0)imsg("Still waiting for end of item %d\n",num);
}
        if (xfrom==0) { /* Initial packet */ 
                if (xitem==NULL) {/* Could not fetch item */
			if (hcache_retry(id)) {
				lprintf(bf,BFSZ-1,"503 Try later, %s\r\n",hcache_error(id));
			} else {
				if (chan_ismid[chan]) lprintf(bf,BFSZ-1,"430 %s\r\n",hcache_error(id));
				else lprintf(bf,BFSZ-1,"423 %s\r\n",hcache_error(id));
			}              
                        chan_write_s(chan,bf);
                        goto finished;
                }
                if (str_len(xitem)==0) return; /* No data available yet */
                imsg("Read %d bytes\n",str_len(xitem));
                mid = get_ahead(xitem,"Message-ID:");
                if (sendhead)   lprintf(bf,BFSZ-1,"220 %d %s article retrieved - head and body follows\015\012",num,mid);
                else            lprintf(bf,BFSZ-1,"222 %d %s article retrieved - body follows\015\012",num,mid);
                chan_write_s(chan,bf);
                if (!sendhead) {
                        /* Remove header from string */
                        hdr_strip(xitem);
                }
        } else {
                if (xitem==NULL) {/* Could not fetch item but part way thru it */
			emsg("Header fetch failed part way %s\n",hcache_error(id));
                        emsg("Closing channel %d as part way thru sending item when something went wrong\n",chan);
			lprintf(bf,BFSZ-1,"503 Fatal error closing link, %s\015\012",hcache_error(id));
		        chan_write_s(chan,bf);
	                nntp_setbusy(chan,FALSE);       
                        chan_close(chan);
                        return;
                }
                if (str_len(xitem)==0) return; /* No data available yet */
        }
        if (finished) str_addsn(xitem,".\015\012");
        chan_write(chan,xitem);
	ml_add(chan,1,str_len(xitem),reason);
	ncmd_pipe(chan,str_len(xitem),"xhdr");

        if (!skip_used) group_used(chan,chan_getgrp(chan),chan_getnum(chan),str_len(xitem));  /* Log use of article by that user */
	if (finished) nntp_setbusy(chan,FALSE); 
        return ;

finished:
        nntp_setbusy(chan,FALSE);       
        
}

int chan_outspeed(int c);
void ncmd_list_finish(int chan)
{
        
        int i;
	int r;
	int tot=0;
	int maxout = chan_outspeed(chan);
	char reason[BFSZ];
        int nsent=0;
        vtrace();
        for (i=busy_from[chan]; i<group_n() && nsent<15000; i++) {
          if (config_true(C_AUTH_SHOWPROT)) goto xx;
          if (chan_canreadgrp(chan,group_name(i))) {
xx:             chan_write_buff(chan,group_getlist(i));
		tot += 30; /* estimate */
                nsent++;
/*		if (tot>maxout) break;  Don't over-flow output buffers and waste memory */
		if (tot>64000) {chan_write_flush(chan); tot = 0;}
		if (tot>maxout) {chan_write_flush(chan); tot = 0;}
          } 
        }
        chan_write_flush(chan); 

        busy_from[chan] = i;
        if (i>=group_n()) {
		if (nsent>0) return;
/*		if (nntp_outsz(chan)>0) {
			imsg("Not sending dot yet as channel still got buffered data %d\n",nntp_outsz(chan));
			return;
		}*/
                chan_write_s(chan,".\015\012");
                nntp_setbusy(chan,FALSE);
        }
	if (!config_true(C_ML_DISABLE)) {
	 r = ml_add(chan,0,tot,reason);
	}
        group_used(chan,-1,0,tot);  /* Log use of article by that user */
	ncmd_pipe(chan,tot,"list");

}
void ncmd_list_names_finish(int chan)
{
        
        int i;
        char bf[BFSZ];
        int nsent=0;
        vtrace();
        for (i=busy_from[chan]; (i<group_n()) && (nsent<500); i++) if (!group_isdeleted(i)) if (group_gettitle(i)!=NULL) {
		if (config_true(C_AUTH_SHOWPROT)) goto xx;
		if (chan_canreadgrp(chan,group_name(i))) {	
xx:			lprintf(bf,BFSZ-1,"%s\t%s\015\012",group_name(i),group_gettitle(i));
			chan_write_s(chan,bf);
			nsent++;
		}
        }
        busy_from[chan] = i;
        if (i>=group_n()) {
                chan_write_s(chan,".\015\012");
                nntp_setbusy(chan,FALSE);       
        }
}
  

void cmd_complete(int chan, int p1, int p2, int from, int to, int state)
{
        vtrace();
        nntp_setbusy(chan,TRUE);
        cmd_mode[chan] = state;
        busy_from[chan] = from;
        busy_to[chan] = to;
        busy_p1[chan] = p1;
        busy_p2[chan] = p2;
}


int ncmd_head(int chan, char *in)
{
        String *head;
        int grp,num;
        char *mid,bf[BFSZ];     

        vtrace();
        if (is_slave()) {
          ncpy(bf,get_word(in,1),BFSZ-1);
          if (bf[0]=='<') { slave_lookup(chan,bf,SL_HEAD); return TRUE; } 
        }

	seq_next(chan,S_HEAD,strlen(in)>4);
        if (!ncmd_findart(chan,in,&grp,&num)) return FALSE;

        if (db_iscancel(grp,num)) {
                imsg("item cancelled %s:%d\n",group_name(grp),num);
                goto gone;
        }
        head = item_head(grp,num);
        if (head==NULL) {
gone:           chan_write_s(chan,art_fail);
                return FALSE;
        }
        
        mid = get_ahead(head,"Message-ID:");
	if (strlen(find_mid)>0) if (strcmpnc(mid,find_mid)!=0) {
		nwrong++;
		emsg("Messageid wrong %s %d %s %s\n",group_name(grp),num,mid,find_mid);
		art_fail = "430 Wrong article found sorry\015\012";
		chan_write_s(chan,art_fail);
		return FALSE;
	}
        lprintf(bf,BFSZ-1,"221 %d %s article retrieved - head follows\015\012",num,mid);
        chan_write_s(chan,bf);

        str_addsn(head,".\015\012");
        chan_write(chan,head);
        return TRUE;
}
int ncmd_flushsite(int chan, char *in)
{
        char p1[BFSZ];
        ncpy(p1,get_word(in,1),199);
        if (feed_flushsite(p1)) {
                chan_write_s(chan,"280 Site flushed\015\012");
        } else {
                chan_write_s(chan,"480 Site does not 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|subscriptions]\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[2000],p1[2000];
        char reason[2000];

        vtrace();
        ncpy(p1,get_word(in,1),700);
        msg_from(chan_getipname(chan));
        msg_id(p1);
        if (!chan_canihave(chan)) {
                emsg("Transfer permission denied for {%s} \n",chan_getipname(chan));
                status_recent("Refused feed, add (%s:ihave:::*) to access.conf to fix",chan_getipname(chan));
                lprintf(bf,2000-1,"480 transfer permission denied (%s) \015\012",chan_getipname(chan));
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (is_readonly()) {
                emsg("slave: Transfer permission denied for {%s}, slave_feed incorrect \n",chan_getipname(chan));
                status_recent("Refused feed, add to slave_feed ...,%s to fix",chan_getipname(chan));
                lprintf(bf,2000-1,"480 transfer permission denied (%s) slave_feed incorrect\015\012",chan_getipname(chan));
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (ispause) {
                lprintf(bf,2000-1,"431 Sorry Paused (%s) \015\012",pause_reason);
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (ispause_ihave) {
                lprintf(bf,2000-1,"431 Sorry Paused (%s) \015\012",pause_reason);
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (!lib_checkspace()) {
                lprintf(bf,2000-1,"431 %s try again later (disk full) \015\012",p1);
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (invalid_mid(p1,reason)) {
                lprintf(bf,2000-1,"438 %s article not wanted - %s\015\012",p1,reason);
                chan_write_s(chan,bf);
                msg_reject(reason);
                chan_gotdup(chan);
                return TRUE;
        }

        if (config_true(C_CHECK_SECOND)) {
                { static int xon;
                        xon = !xon;
                        if (xon) goto failit;
                }
        }
        
        if ((hist_gotit2(p1,chan)) || (*p1!='<') ) { 
failit:
                lprintf(bf,2000-1,"438 %s already have it\015\012",p1);
                chan_write_s(chan,bf);
                msg_reject("Article in history file");
                chan_gotdup(chan);
        } else {
                lprintf(bf,2000-1,"238 %s not here, please send it to me\015\012",p1);
                chan_write_s(chan,bf);
        }
        return TRUE;
}
int ncmd_check_result(int chan, char *mid, char *rs)
{
        char reason[2000];

        msg_from(chan_getipname(chan));
        msg_id(mid);
        if (!chan_canihave(chan)) {
                emsg("Transfer permission denied for {%s} \n",chan_getipname(chan));
                status_recent("Refused feed, add (%s:ihave:::*) to access.conf to fix",chan_getipname(chan));
                lprintf(rs,BFSZ-1,"480 transfer permission denied (%s) \015\012",chan_getipname(chan));
                return TRUE;
        }
        if (is_readonly()) {
                emsg("slave: Transfer permission denied for {%s}, slave_feed incorrect \n",chan_getipname(chan));
                status_recent("Refused feed, add to slave_feed ...,%s to fix",chan_getipname(chan));
                lprintf(rs,BFSZ-1,"480 transfer permission denied (%s) slave_feed incorrect\015\012",chan_getipname(chan));
                return TRUE;
        }
        if (ispause) {
                lprintf(rs,BFSZ-1,"431 Sorry Paused (%s) \015\012",pause_reason);
                return TRUE;
        }
        if (ispause_ihave) {
                lprintf(rs,BFSZ-1,"431 Sorry Paused (%s) \015\012",pause_reason);
                return TRUE;
        }
        if (!lib_checkspace()) {
                lprintf(rs,BFSZ-1,"431 %.100s try again later (disk full) \015\012",mid);
                return TRUE;
        }
        if (invalid_mid(mid,reason)) {
                lprintf(rs,BFSZ-1,"438 %s article not wanted - %s\015\012",mid,reason);
                msg_reject(reason);
                chan_gotdup(chan);
                return TRUE;
        }        
        if ((hist_gotit2(mid,chan)) || (*mid!='<') ) { 
                lprintf(rs,BFSZ-1,"438 %s already have it\015\012",mid);
                msg_reject("Article in history file");
                chan_gotdup(chan);
        } else {
                lprintf(rs,BFSZ-1,"238 %s not here, please send it to me\015\012",mid);
        }
        return TRUE;
}
int ncmd_ihave_result(int chan, char *mid, char *rs)
{
        char reason[2000];

        msg_from(chan_getipname(chan));
        msg_id(mid);
        if (!chan_canihave(chan)) {
                emsg("Transfer permission denied for {%s} \n",chan_getipname(chan));
                status_recent("Refused feed, add (%s:ihave:::*) to access.conf to fix",chan_getipname(chan));
                lprintf(rs,BFSZ-1,"480 transfer permission denied (%s) \015\012",chan_getipname(chan));
                return TRUE;
        }
        if (is_readonly()) {
                emsg("slave: Transfer permission denied for {%s}, slave_feed incorrect \n",chan_getipname(chan));
                status_recent("Refused feed, add to slave_feed ...,%s to fix",chan_getipname(chan));
                lprintf(rs,BFSZ-1,"480 transfer permission denied (%s) slave_feed incorrect\015\012",chan_getipname(chan));
                return TRUE;
        }
        if (ispause) {
                lprintf(rs,BFSZ-1,"431 Sorry Paused (%s) \015\012",pause_reason);
                return TRUE;
        }
        if (ispause_ihave) {
                lprintf(rs,BFSZ-1,"431 Sorry Paused (%s) \015\012",pause_reason);
                return TRUE;
        }
        if (!lib_checkspace()) {
                lprintf(rs,BFSZ-1,"431 %s try again later (disk full) \015\012",mid);
                return TRUE;
        }
        if (invalid_mid(mid,reason)) {
                lprintf(rs,BFSZ-1,"435 article not wanted badmid - %s\015\012",reason);
                msg_reject(reason);
                chan_gotdup(chan);
                return TRUE;
        }        
        if ((hist_gotit2(mid,chan)) || (*mid!='<') ) { 
                lprintf(rs,BFSZ-1,"435 already have it\015\012");
                msg_reject("Article in history file");
                chan_gotdup(chan);
        } else {
                lprintf(rs,BFSZ-1,"335 send article to be transferred.  End with <CR-LF>.<CR-LF>\015\012");
		return TRUE;
        }
        return FALSE;
}
void check_world(int chan)
{
        char bf[BFSZ];
        char zzpost[5000],zzread[5000];
        int zipost,ziread,zihave,isauth;
        strcpy(bf,"dummyname");
        access_check(bf,bf,"","",
                zzpost,zzread,
                &zipost,&ziread,&zihave,
                &isauth);

        if (isauth) if (zipost || ziread) if (group_match(zzpost,"anygroup")) {
                emsg("WARNING: your access.conf appears to allow anyone to read and post to your server\n");
                emsg("If this is ok set in dnews.conf  allow_world true\n");
                emsg("Otherwise examine ACCESS.CONF and remove lines like '*:read,post:::*\n");
                chan_write_s(chan,"\r\nWARNING: your access.conf appears to allow \r\nanyone to read and post to your server\r\n");
                chan_write_s(chan,"If this is ok set in dnews.conf    allow_world true\r\n");
                chan_write_s(chan,"Otherwise examine ACCESS.CONF and remove lines like '*:read,post:::*'\r\n");
        }
}
void chan_setisihave(int c, int x);
int ncmd_takethis(int chan, char *in)
{
        char p1[BFSZ];
        if (config_true(C_TAKETHIS_FAIL)) {
                chan_write_s(chan,"502 Takethis Fail Enabled\r\n");
                return TRUE;
        }
	chan_setisihave(chan,TRUE);

        chan_setreject(chan,FALSE);
        ihavein_clear(chan);
        big_init(chan);
        ncpy(p1,get_word(in,1),199);
        ifstrfree(chan_take[chan]);
        chan_take[chan] = mystrdup(p1,SD_NCMD);
        chan_setstate(chan,C_IHAVE);
        chan_postchan[chan] = -1;
        chan_postslave[chan] = FALSE;
        return TRUE;
}
int ncmd_chan_init(int chan)
{
	id_close(chan);
	chan_wait_for_dot[chan] = FALSE;
	busy_senddot[chan] = FALSE;
	seq_init(chan);
	return TRUE;
}
int ncmd_xdtakethis(int chan, char *in)
{
        char bf[BFSZ];
	char ipnum[BFSZ];
	char lcl[BFSZ];
        ihavein_clear(chan);
        big_init(chan);

	ncpy(ipnum,get_word(in,3),100);
	ncpy(lcl,get_word(in,4),100);
	if (strlen(ipnum)>0) {
		imsg("xdtakethis from user (%s) interface (%s)\n",ipnum,lcl);
		chan_saveipnum(chan);
		chan_setipnum(chan,ipnum);
		nntp_setlocalip(chan,lcl);      
		chan_setdom(chan,NULL); /* Clear it so it will be set correctly on next call */
		dom_setdom(chan);
	}

     
        chan_postchan[chan] = atoi(get_word(in,1));
        chan_postslave[chan] = TRUE;
        if (ispause) {
                lprintf(bf,BFSZ-1,"xdpost %d 441 Sorry Paused, try later (%s) \015\012",chan_postchan[chan],pause_reason);
                chan_write_s(chan,bf);
                return TRUE;
        }
        chan_setstate(chan,C_POST);
        return TRUE;
}
static int rej_silent=FALSE;
void set_rej_silent(int x)
{
        rej_silent = x;
}
int is_silent(void)
{
        int r = rej_silent;
        rej_silent = FALSE;
        return r;
}


int ncmd_postonly(String *xitem);
int ncmd_gettake(int chan,String *anitem, int toobig)
{
        char bf[BFSZ];
        char *p1 = chan_take[chan];     
        int ecode;
        static int donewarn;
        char toslave[BFSZ];
        int pchan = chan_postchan[chan];
 
        if (pchan>=0) {
                imsg("post: Got post from slave\n");
                lprintf(toslave,BFSZ-1,"xdpost %d ",pchan);
                chan_postchan[chan] = -1;
        } else {
                strcpy(toslave,"");
        }
        
        msg_from(chan_getipname(chan));
        msg_id(p1);
        chan_setstate(chan,C_CMD);
        if (feed_byposting()) {
                if (!donewarn) imsg("ncmd: incoming IHAVE with a POSTING outward is not recommended\n");
                donewarn = TRUE;
        }
        if (is_readonly()) {
                emsg("slave: Transfer permission denied for {%s} \n",chan_getipname(chan));
                lprintf(bf,BFSZ-1,"%s480 transfer permission denied (%s) slave\015\012",toslave,chan_getipname(chan));
                chan_write_s(chan,bf);
                return TRUE;
        }

        if (pchan<0) {
        if (!chan_canihave(chan)) {
                emsg("Transfer permission denied for {%s} \n",chan_getipname(chan));
                status_recent("Refused feed, add (%s:ihave:::*) to access.conf to fix",chan_getipname(chan));
                lprintf(bf,BFSZ-1,"480 transfer permission denied (%s)\015\012",chan_getipname(chan));
                chan_write_s(chan,bf);
                msg_reject("Transfer permission denied");
                ifstrfree(chan_take[chan]);
                return TRUE;
        }}
        if (toobig) {
                ecode = atoi(config_get(C_NNTP_BIGREJECT2));
                if (ecode==0) ecode=439; 
                lprintf(bf,BFSZ-1,"%s%d %.400s transfer failed - do not try again later\015\012",toslave,ecode,p1);
                chan_write_s(chan,bf);
                msg_reject("Article exceeded ITEM_MAX size");
                return TRUE;
        }

        if (!lib_checkspace()) {
                lprintf(bf,BFSZ-1,"%s431 sorry disk full - try again later\015\012",toslave);
                chan_write_s(chan,bf);
                return TRUE;
        }

        if (ispause) {
                chan_gotpost(chan,FALSE,0);
                lprintf(bf,BFSZ-1,"%s431 Sorry Paused (%s) \015\012",toslave,pause_reason);
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (ispause_ihave) {
                chan_gotpost(chan,FALSE,0);
                lprintf(bf,BFSZ-1,"%s431 Sorry Paused (%s) \015\012",toslave,pause_reason);
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (anitem==NULL) {
                chan_gotpost(chan,FALSE,0);
                lprintf(bf,BFSZ-1,"%s431 %s transfer failed - try again later (Disk error writing temp file)\015\012",toslave,p1);
                chan_write_s(chan,bf);
        } else {
          if (config_true(C_POSTONLY)) {
            if (chan_postslave[chan]) {
              if (ncmd_postonly(anitem)) {
                lprintf(bf,BFSZ-1,"%s239 %.400s master accepted item\015\012",toslave,p1);
                chan_write_s(chan,bf);          
              } else {
                lprintf(bf,BFSZ-1,"%s431 %.400s master rejected item\015\012",toslave,p1);
                chan_write_s(chan,bf);
              }
              goto done;
            }
          }
          if (chan_isdrop(chan)) {
               lprintf(bf,BFSZ-1,"%s439 %.800s transfer failed - do not try again later (timeout)\015\012",toslave,p1);
               chan_write_s(chan,bf);
               emsg("Dropping connection to channel %d as we have lost sync\n",chan);
               chan_close(chan);
               goto done;               
          }
          item_setchan(chan);
          if (!item_suck(anitem)) {
                item_setchan(-1);
                chan_gotpost(chan,FALSE,str_len(anitem));
                if (rej_silent) {
                        msg_show("Spam rejection");
                        rej_silent = FALSE;
                }
                if (item_tryagain()) {
                  lprintf(bf,BFSZ-1,"%s431 %.800s transfer failed - try again later (%s)\015\012",toslave,p1,msg_reason());
                  chan_write_s(chan,bf);
                } else {
                   lprintf(bf,BFSZ-1,"%s439 %.800s transfer failed - do not try again later (%s)\015\012",toslave,p1,msg_reason());
                  chan_write_s(chan,bf);
                }
          } else {
                item_setchan(-1);
                chan_gotpost(chan,TRUE,str_len(anitem));
                lprintf(bf,BFSZ-1,"%s239 %.800s article transferred ok\015\012",toslave,p1);
                chan_write_s(chan,bf);
          }
        }
done:;
        ifstrfree(chan_take[chan]);
        chan_take[chan] = NULL;
        return TRUE;
}
int ncmd_postonly(String *anitem)
{
        if (post_local(anitem,NULL,FALSE)) {
                dmsg("post: Article posted to slave posting mechanism \n");
                feed_doit_if();
                return TRUE; 
        } 
        return FALSE;
}
static int lastihaveok;
int lib_ispause(void)
{
        return ispause;
}

int invalid_mid(char *mid, char *reason)
{
        int len;
        if (*mid!='<') {
                strcpy(reason,"Missing opening angle bracket");
                return TRUE;
        }
        len = strlen(mid);
        if (len>250) { /* Was 98, lets do some tests */
                strcpy(reason,"MessageID is too long");
                return TRUE;
        }
	if (len==0) {
                strcpy(reason,"MessageID is blank");
		return TRUE;
	}
        if (mid[len-1]!='>') {
                strcpy(reason,"Closing angle bracket missing");
                return TRUE;
        }
        if ( (strchr(mid,'>')-mid+1)!=len)  {
                lprintf(reason,2000-1,"Closing angle bracket at %dth char of MID",(strchr(mid,'>')-mid+1));
                return TRUE;
        }
        return FALSE;
}
int ncmd_ihave(int chan, char *in)
{
        char p1[BFSZ];
        static int donewarn;
        char reason[BFSZ];
        char bf[BFSZ];
        vtrace();
        ihavein_clear(chan);
        big_init(chan);

	chan_setisihave(chan,TRUE);

        chan_setreject(chan,FALSE);
        if (feed_byposting()) {
                if (!donewarn) imsg("ncmd: incoming ihave with a POSTING outward feed is a really bad idea\n");
                donewarn = TRUE;
        }
        if (is_readonly()) {
                emsg("slave: Transfer permission denied for {%s} \n",chan_getipname(chan));
                lprintf(bf,BFSZ-1,"480 transfer permission denied (%s) slave\015\012",chan_getipname(chan));
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (!chan_canihave(chan)) {
                emsg("Transfer permission denied for {%s} \n",chan_getipname(chan));
                status_recent("Refused feed, add (%s:ihave:::*) to access.conf to fix",chan_getipname(chan));
                lprintf(bf,BFSZ-1,"480 transfer permission denied (%s)\015\012",chan_getipname(chan));
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (ispause) {
                lprintf(bf,BFSZ-1,"436 Sorry Paused (%s) \015\012",pause_reason);
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (ispause_ihave) {
                lprintf(bf,BFSZ-1,"436 Sorry Paused (%s) \015\012",pause_reason);
                chan_write_s(chan,bf);
                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),199);
        msg_from(chan_getipname(chan));
        msg_id(p1);
        if (speed_test) {
                chan_setstate(chan,C_IHAVE);
                chan_write_s(chan,"335 send article to be transferred.  End with <CR-LF>.<CR-LF>\015\012");
                return TRUE;
        }
        if (config_true(C_IHAVE_GUESS)) { 
                if (lastihaveok) {
                        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); /* Do lookup now, so it will know later */
                        return TRUE;    
                }
        }
        if (invalid_mid(p1,reason)) {
                lprintf(bf,BFSZ-1,"435 article not wanted badmid2 - %s\015\012",reason);
                chan_write_s(chan,bf);
                msg_reject(reason);
                chan_gotdup(chan);
                lastihaveok = FALSE;
                return TRUE;
        }
        if ((hist_gotit2(p1,chan)) || (*p1!='<') ) { 
                chan_write_s(chan,"435 article not wanted - do not send it hist\015\012");
                msg_reject("Article in history file or not valid");
                chan_gotdup(chan);
                lastihaveok = FALSE;
        } else {
                lastihaveok = TRUE;
                chan_postchan[chan] = -1;
                chan_setstate(chan,C_IHAVE);
                chan_write_s(chan,"335 send article to be transferred.  End with <CR-LF>.<CR-LF>\015\012");
        }
        return TRUE;
}
int ncmd_item_suck(int chan, char *mid, String *anitem, char *res, int toobig, int isihave)
{
	char toslave[BFSZ];
	char bf[BFSZ];
	vtrace();

	strcpy(toslave,"");
        msg_from(chan_getipname(chan));
        msg_id(mid);

/*	db_test_nulls(str_s(anitem),str_len(anitem),"somegroup",1,"ncmd_item_suck");*/

        if (is_readonly()) {
                emsg("slave: Transfer permission denied for {%s} \n",chan_getipname(chan));
                lprintf(res,BFSZ-1,"%s480 transfer permission denied (%s) slave\015\012",toslave,chan_getipname(chan));
                return TRUE;
        }
        if (!chan_canihave(chan)) {
                emsg("Transfer permission denied for {%s} \n",chan_getipname(chan));
                status_recent("Refused feed, add (%s:ihave:::*) to access.conf to fix",chan_getipname(chan));
                lprintf(res,BFSZ-1,"480 transfer permission denied (%s)\015\012",chan_getipname(chan));
                msg_reject("Transfer permission denied");
		return TRUE;
        }
        if (!lib_checkspace()) {
                lprintf(res,BFSZ-1,"%s431 sorry disk full - try again later\015\012",toslave);
                return TRUE;
        }

        if (ispause) {
                chan_gotpost(chan,FALSE,0);
                lprintf(res,BFSZ-1,"%s431 Sorry Paused (%s) \015\012",toslave,pause_reason);
                chan_write_s(chan,bf);
                return FALSE;
        }
        if (ispause_ihave) {
                chan_gotpost(chan,FALSE,0);
                lprintf(res,BFSZ-1,"%s431 Sorry Paused (%s) \015\012",toslave,pause_reason);
                return TRUE;
        }
        if (toobig) {
                chan_gotpost(chan,FALSE,1000000);
                lprintf(res,BFSZ-1,"%s439 %.900s transfer failed item too big\015\012",toslave,mid);
                if (isihave) lprintf(res,BFSZ-1,"%s437 transfer failed item too big\015\012",toslave);
                return TRUE;
        }

        item_setchan(chan);
	vtrace();
        if (!item_suck(anitem)) {
		vtrace();
                item_setchan(-1);
                chan_gotpost(chan,FALSE,str_len(anitem));
                if (rej_silent) {
                        msg_show("Spam rejection");
                        rej_silent = FALSE;
                }
                if (item_tryagain()) {
                  lprintf(res,BFSZ-1,"%s431 %.900s transfer failed - try again later (%s)\015\012",toslave,mid,msg_reason());
                  if (isihave) lprintf(res,BFSZ-1,"%s436 %.900s transfer failed - try again later (%s)\015\012",toslave,mid,msg_reason());
                  return TRUE;
                } else {
                  lprintf(res,BFSZ-1,"%s439 %.900s transfer failed - do not try again later (%s)\015\012",toslave,mid,msg_reason());
                  if (isihave) lprintf(res,BFSZ-1,"%s437 %.900s transfer failed - do not try again later (%s)\015\012",toslave,mid,msg_reason());
                  return TRUE;
                }
        } else {
		vtrace();
                item_setchan(-1);
                chan_gotpost(chan,TRUE,str_len(anitem));
                lprintf(res,BFSZ-1,"%s239 %.900s article transferred ok\015\012",toslave,mid);
                if (isihave) lprintf(res,BFSZ-1,"%s235 %.900s article transferred ok\015\012",toslave,mid);
		vtrace();
                return TRUE;
        }
	vtrace();
        return TRUE;
}
static char *xreplic[MAX_CHAN];
int ncmd_xreplic(int chan, char *in)
{
        static int donewarn;
        char bf[XBFSZ];
        vtrace();
        ihavein_clear(chan);
        big_init(chan);

        if (is_readonly()) {
                emsg("slave: Transfer permission denied for {%s} \n",chan_getipname(chan));
                lprintf(bf,XBFSZ-1,"480 transfer permission denied (%s) slave\015\012",chan_getipname(chan));
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (!chan_canihave(chan)) {
                emsg("Transfer permission denied for {%s} \n",chan_getipname(chan));
                status_recent("Refused feed, add (%s:ihave:::*) to access.conf to fix",chan_getipname(chan));
                lprintf(bf,XBFSZ-1,"480 transfer permission denied (%s)\015\012",chan_getipname(chan));
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (ispause) {
                lprintf(bf,XBFSZ-1,"436 Sorry Paused (%s) \015\012",pause_reason);
                chan_write_s(chan,bf);
                return TRUE;
        }
        if (!lib_checkspace()) {
                chan_write_s(chan,"436 sorry disk full - try again later\015\012");
                return TRUE;
        }
        if (xreplic[chan]==NULL) xreplic[chan] = mymalloc(XBFSZ);
        ncpy(xreplic[chan],get_word(in,1),XBFSZ-10);
        msg_from(chan_getipname(chan));
        msg_id("xreplic");
        chan_postchan[chan] = -1;
        chan_setstate(chan,C_IHAVE);
        chan_write_s(chan,"335 send article to be transferred.  End with <CR-LF>.<CR-LF>\015\012");
        return TRUE;
}

int ihavein_clear(int i)
{
/*	imsg("ihavein_clear (%d)\n",i);*/
        big_free(i);
        pop_free(i);
        chan_postslave[i] = FALSE;
        chan_authgrp[i] = -1;
        chan_donedeny[i] = FALSE;
        chan_nsucked[i] = 0;
        chan_authcnt[i] = 0;
	ihavein_failed[i] = FALSE;
        nntp_setbusy(i,FALSE);
	if (cmd_mode[i]==M_THREAD) imsg("ihavein_clear [%d] clearing thread flag!!!\n",i);
        cmd_mode[i] = -1;
        ifstrfree(chan_wronghost[i]);
	chan_wronghost[i] = NULL;


        return TRUE;
}
/*
#include <io.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
*/
int ncmd_getihave(int chan, char *in, int sz)
{
        int ecode;
        char toobigmsg[BFSZ];
        char bf[BFSZ];
        static int ihavein_failed[MAX_CHAN];
        String *get;
        int x = FALSE;
	static int fred = -1;

        if (*in=='.') if (strcmp(in,".\015\012")==0)  x = TRUE;
/*
if (x) {
	char *p1 = chan_take[chan];     
	char bf[BFSZ];
             
        msg_from(chan_getipname(chan));
        msg_id(p1);
        chan_setstate(chan,C_CMD);
        item_setchan(-1);
        chan_gotpost(chan,TRUE,1);
        lprintf(bf,BFSZ-1,"239 %s article transferred ok\015\012",p1);
	chan_write_s(chan,bf);
        iffree(chan_take[chan]);
        chan_take[chan] = NULL;
        return TRUE;
} else {
	memmove(zbf,in,sz);
	if (fred<0) fred = _open("a.a",_O_CREAT | _O_BINARY | _O_SEQUENTIAL | _O_WRONLY);
//	if (fred==NULL) fred = fopen("a.a","wb");
	write(fred,zbf,sz);
	return TRUE;
}
*/
        if (x) {
                msg_from(chan_getipname(chan));
                if (config_true(C_IHAVE_BUG)) {
                        emsg("Ignoring end of item due to IHAVE_BUG\n");
                        return TRUE;
                }
                if (chan_take[chan]!=NULL) {
                        stat_start(X_IHAVE_DO);
                        ncmd_gettake(chan,big_get(chan),ihavein_failed[chan]); 
                        ihavein_failed[chan] = FALSE;
                        big_free(chan);
                        stat_end(X_IHAVE_DO);
                        return TRUE;
                }
                if (chan_isdrop(chan)) {
                        if (xreplic[chan]!=NULL) strcpy(xreplic[chan],"");
                        ihavein_failed[chan] = FALSE;
                        ecode=437; 
                        lprintf(toobigmsg,BFSZ-1,"%d article rejected - do not try again, (timeout)\015\012",ecode);
                        chan_write_s(chan,toobigmsg);
                        big_free(chan);
                        msg_reject("Channel timed out no . received ");
                        chan_setstate(chan,C_CMD);
                        return TRUE;
                }
                if (ihavein_failed[chan]) {
                        if (xreplic[chan]!=NULL) strcpy(xreplic[chan],"");
                        ihavein_failed[chan] = FALSE;
                        ecode = atoi(config_get(C_NNTP_BIGREJECT));
                        if (ecode==0) ecode=437; 
                        lprintf(toobigmsg,BFSZ-1,"%d article rejected - do not try again, too BIG\015\012",ecode);
                        chan_write_s(chan,toobigmsg);
                        big_free(chan);
                        msg_reject("Article exceeded ITEM_MAX size");
                        chan_setstate(chan,C_CMD);
                        return TRUE;
                } 
                stat_start(X_IHAVE_DO);
                if (speed_test) {
                        chan_write_s(chan,"235 article transferred ok\015\012");
                } else {
                  get = big_get(chan);
                  if (get==NULL) {
                        if (xreplic[chan]!=NULL) strcpy(xreplic[chan],"");
                        chan_gotpost(chan,FALSE,0);
                        lastihaveok = FALSE;
                        chan_write_s(chan,"437 article rejected by big logic\015\012");
                  } else {
                    if (xreplic[chan]!=NULL) if (strlen(xreplic[chan])>0) {
                        group_setxreplic(xreplic[chan]);        
                    }
                    item_setchan(chan);
                    vtrace();
                    if (!item_suck(get)) {
                        item_setchan(-1);
                        group_setxreplic("");
                        chan_gotpost(chan,FALSE,str_len(get));
                        lastihaveok = FALSE;
                        if (rej_silent) {
                                msg_show("Spam rejection");
                                rej_silent = FALSE;
                        }
                        if (item_tryagain()) {
                                chan_write_s(chan,"436 transfer failed - try again later\015\012");
                        } else {
                                lprintf(bf,BFSZ-1,"437 article rejected - do not try again (%s)\015\012",msg_reason());
                                chan_write_s(chan,bf);
                        }
                    } else {
                        item_setchan(-1);
                        group_setxreplic("");
                        lastihaveok = TRUE;
                        chan_gotpost(chan,TRUE,big_len(chan));
                        chan_write_s(chan,"235 article transferred ok\015\012");
                    }
                   }
                } 
                chan_setstate(chan,C_CMD);
                big_free(chan);
                if (xreplic[chan]!=NULL) strcpy(xreplic[chan],"");
                stat_end(X_IHAVE_DO);
        } else {
                stat_start(X_IHAVE_DATA);
                if (big_len(chan) >= MAX_STRLEN) ihavein_failed[chan] = TRUE;
                else big_add(chan,in,sz);
                stat_end(X_IHAVE_DATA);
        }
        if (config_true(C_FEED_BUG)) {
         if (big_len(chan) > 10000) {
                emsg("Closing channel like fake bug\n");
                chan_close(chan);
         }
        }
        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);
        if (num==start) goto skipit;
        num = g_find_last(grp,num);
        for (;num>=start;) {
                head = item_head(grp,num);
                if (head!=NULL) break;
                num = g_find_last(grp,num);
        }
        if (head==NULL) {
skipit:         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:");
        lprintf(bf,BFSZ-1,"223 %d %s article retrieved - request text separately\015\012",num,mid);
        chan_write_s(chan,bf);
        return TRUE;
}

/* Syntax of dsearch extension:
        dsearch n1-n2 word1,word2,word3

  Extend the syntax:
      dsearch n1-n2 word1,word2,word3  groups=wild_list after=gmt before=gmt and


  Implementation:
	groups, Pass wild_list to fts_search, create mask...
	after,before get head info, and compare to date of bucket.

Response:
        229 Matches: 2323, Each: 23 43 111
        group itemnum score
        group itemnum score
        group itemnum score
        .
*/
int ncmd_dsearch_test(int chan, char *word, int n)
{
	char bf[BFSZ];
	int i;
	if (n<=0) n=1;
	for (i=0; i<n; i++) {
		lprintf(bf,BFSZ-1,"xdsearch 0-100 %s",word);
		ncmd_dsearch(chan,bf);
	}
	return TRUE;

}
char *get_fts_dir(void);
int ncmd_dsearch(int chan, char *in)
{
        char range[BFSZ],slist[BFSZ],bf[BFSZ];
        static char *r_name[200];
        int r_score[200],r_num[200];
        int r_terms[100];
        int from,to,grp;
        static int gotfts;
        char *terms[100], nterms;
        String *ss;
        char *s;
        char ftsgroup[BFSZ];
        int g;
        char xref[BFSZ];
        char xbuff[5001];
        int i,j;
	int isand = FALSE;
	char gwild[BFSZ];
	int stype = 1;
	int xsince,xbefore;
	int r_xtime[200];

	strcpy(gwild,"");

        ncpy(range,get_word(in,1),BFSZ-1); strlwr(range);

        imsg("Range {%s} \n",range);
        ncpy(slist,get_word(in,2),BFSZ-1); strlwr(slist);
        from = atoi(range);
        s = strchr(range,'-'); if (s==NULL) s = range;
        to = atoi(s+1);
        if (to-50>from) to = from+50; /* Limit return to 51 items */
        if (from<=0) from = 1;
        if (to<from) to = from+10;
        from--; to--;
        imsg("Range %d %d \n",from,to);
        s = strtok(slist,",");
        for (nterms=0;s!=NULL;s=strtok(NULL,",")) {
                terms[nterms++] = mystrdup(s,SD_NCMD);
                if (nterms>20) break;
        }


	/* Options 
		groups=wild_list 
		after=gmt before=gmt 
		sort=score,date,group
		match=and
	 */
	xbefore = xsince = 0;
	stype = 1;
	for (i=3; i<10; i++) {
		s = get_word(in,i);
		strlwr(s);
		imsg("search option [%d] = %s\n",i,s);
		if (strncmp(s,"groups=",7)==0) {
			ncpy(gwild,s+7,BFSZ-10);
		} else if (strncmp(s,"since=",6)==0) {
			xsince = atoi(s+6);
		} else if (strncmp(s,"before=",7)==0) {
			xbefore = atoi(s+7);
		} else if (strncmp(s,"sort=",5)==0) {
			if (strncmp(s+5,"score",5)==0) stype = 1;
			else if (strncmp(s+5,"date",4)==0) stype = 2;
			else if (strncmp(s+5,"group",5)==0) stype = 3;
			else emsg("Invalid search sort option (%s)\n",s);
		} else if (strncmp(s,"match=and",9)==0) {
			isand = TRUE;
		} else if (strlen(s)==0) {
			;
		} else emsg("Invalid search option (%s)\n",s);
	}
        
        imsg("Start search groups=%s, since %d before %d sort=%d isand=%d\n"
		,gwild,xsince,xbefore,stype,isand);

        fts_search(terms,nterms,isand,gwild,xsince,xbefore,stype
		,r_terms,from,to,r_name,r_num,r_score,r_xtime);
	
	
	imsg("Finish  search %d matches\n",r_terms[nterms]);

	if (r_terms[nterms]==0) fts_trigger(); /* Make it write the index if possible*/

        lprintf(bf,BFSZ-1,"229 Matches: %d, Individuals: ",r_terms[nterms]);
        for (i = 0; i<nterms; i++) {
                lprintf(bf+strlen(bf),BFSZ-1,"%d ",r_terms[i]);
        }
        strcat(bf,"\015\012"); 
        chan_write_s(chan,bf);
        for (i=from; i<to; i++) {
                j = i-from;
                if (r_name[j]!=NULL) {
                        grp = group_find(r_name[j]);
                        if (grp<0) continue;
			if (!config_true(C_AUTH_SHOWPROT)) 
			  if (!chan_canreadgrp(chan,group_name(grp))) {
				dmsg("search: access blocked\n");
				continue;
			}

                        ss = item_xover(grp,r_num[j],i!=from);
                        if (ss==NULL) {
				dmsg("search: item missing\n");
				continue;
			}
                        ncpy(xbuff,str_s(ss),4000);
                        s = strchr(xbuff,'\015');
                        if (s!=NULL) *s = 0;

/*imsg("found %s:%d age %d\n",r_name[j],r_num[j],r_xtime[j]);*/
                        /* -- Find the cached group if possible ------- */
                        lprintf(ftsgroup,BFSZ-1,"%s:%d",r_name[j],r_num[j]);
                        s = item_getahead(grp,r_num[j],"Xref");
                        if (s==NULL) goto doit; if (*s==0) goto doit;
                        ncpy(xref,s,BFSZ-1);
                        s = strtok(xref," :\r\n\t"); if (s==NULL) goto doit;
                        s = strtok(NULL," :\r\n\t"); if (s==NULL) goto doit;
                        for (;s!=NULL; ) {
                                g = group_find(s);
                                if (s!=NULL) s = strtok(NULL," :\r\n\t");
                                if (g>=0) if (group_iscached(g)) {
                                        lprintf(ftsgroup,BFSZ-1,"%s:%s",group_name(g),s);
                                        dmsg("ncmd: Cached group is %s\n",ftsgroup);
                                        goto doit;
                                }
                                s = strtok(NULL," :\r\n\t"); 
                        }
                
                        /* We now have the cached group for that item */
doit:                   lprintf(bf,BFSZ-1,"\tfts_score: %d\tfts_group: %s\t\015\012",r_score[j],ftsgroup);
                        strcat(xbuff,bf);
                        chan_write_s(chan,xbuff);
                } 
        }
        chan_write_s(chan,".\015\012");
        return TRUE;
}
int ncmd_dlist(int chan, char *in)  /* dlist rec. */
{
        int i;
        int ndir=0;
        char bf[BFSZ];
        char bf2[BFSZ];
        char p1[BFSZ];
        char last[BFSZ];
        int ii;
        int len;
        char *s;

	strcpy(last,"");
        chan_write_s(chan,"215 dlist follows\015\012");
        ncpy(p1,get_word(in,1),BFSZ-1); strlwr(p1);
        if (strlen(p1)>0) if (p1[strlen(p1)-1]!='.') strcat(p1,".");
        len = strlen(p1);
        group_sort();
        for (ii=0; ii<group_n(); ii++) {
                i = group_ord(ii);
                if (group_isdeleted(i)) continue;


	        if (!config_true(C_AUTH_SHOWPROT)) 
	          if (!chan_canreadgrp(chan,group_name(i))) continue;


                if (strncmp(group_namelc(i),p1,len)==0) {
                        ncpy(bf,group_namelc(i),BFSZ-1);
                        s = strchr(bf+len,'.');
                        if (s==NULL) {
                                chan_write_buff(chan,group_getlist2(i));
                        } else {
                                *s = 0;
                                if (strcmp(bf,last)==0) ndir++;
                                else {
                                        if (ndir>0) {
                                                lprintf(bf2,BFSZ-1,"+ %s %d\015\012",last,ndir);
                                                chan_write_buff(chan,bf2);
                                        }
                                        ndir = 1;
                                        ncpy(last,bf,BFSZ-1);
                                } 
                        }
                }               
        } 
        if (ndir>0) {
                lprintf(bf2,BFSZ-1,"+ %s %d\015\012",last,ndir);
                chan_write_buff(chan,bf2);
                ndir = 0;
        }
        ncpy(last,bf,BFSZ-1);

        chan_write_flush(chan); 

        chan_write_s(chan,".\015\012");
        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 *s;
        FILE *f;
        char p2[BFSZ];
	char bf2[BFSZ];
        ncpy(bf,get_word(in,1),BFSZ-1); strlwr(bf);
        ncpy(p2,get_word(in,2),BFSZ-1);
        strlwr(p2);
        if ( (strcmp(bf,"xactive")==0) && (strlen(p2)>0) ) {
                if (strchr(p2,'*')!=NULL) {
                        lprintf(bf,BFSZ-1,"215 list active %s\015\012",p2); chan_write_s(chan,bf);
                        for (i=0; i<group_n(); i++) {
                         if (group_match(p2,group_namelc(i))) {
                          if (chan_canreadgrp(chan,group_name(i))  || config_true(C_AUTH_SHOWPROT) ) {
                                chan_write_s(chan,group_getlistx(i));
                          } 
                         }
                        }
                } else {
                        i = group_find(p2);
                        if (i<0) {
                                chan_write_s(chan,"503 newsgroup not found\015\012");
                                return TRUE;
                        } 
                        lprintf(bf,BFSZ-1,"215 list active %s\015\012",p2); chan_write_s(chan,bf);
                        chan_write_s(chan,group_getlistx(i));
                }
        } else if ( (strcmp(bf,"active")==0) && (strlen(p2)>0) ) {
                if (strchr(p2,'*')!=NULL) {
                        lprintf(bf,BFSZ-1,"215 list active %s\015\012",p2); chan_write_s(chan,bf);
                        for (i=0; i<group_n(); i++) {
                         if (group_match(p2,group_namelc(i))) {
                          if (chan_canreadgrp(chan,group_name(i)) || config_true(C_AUTH_SHOWPROT) ) {
                                chan_write_s(chan,group_getlist(i));
                          } 
                         }
                        }
                } else {
                        i = group_find(p2);
                        if (i<0) {
                                chan_write_s(chan,"503 newsgroup not found\015\012");
                                return TRUE;
                        } 
                        lprintf(bf,BFSZ-1,"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");
                cmd_complete(chan,0,0,0,0,M_LIST);
                ncmd_list_finish(chan);
                return TRUE;
/*              if (config_true(C_LIST_READONLY)) {
                        for (i=0; i<group_n(); i++) {
                          if (chan_canreadgrp(chan,group_name(i))) {
                                chan_write_buff(chan,group_getlist(i));
                          } 
                        }
                } else {
                        for (i=0; i<group_n(); i++) {
                                chan_write_buff(chan,group_getlist(i));
                        }
                }
                chan_write_flush(chan); 
  */
        } else if (strcmp(bf,"active.times")==0) {
                group_times_flush();
                f = fopen(DNEWS_ACTIVE_TIMES,"r");
                if (f==NULL) goto not_available;
                chan_write_s(chan,"215 list of active.times follows\015\012");
                for (i=0; !feof(f); i++) {
                        if (fgets(bf,BFSZ-10,f)==NULL) break;
                        s = strchr(bf,'\r'); if (s!=NULL) *s = 0;
                        s = strchr(bf,'\n'); if (s!=NULL) *s = 0;
                        if (strlen(bf)==0) continue;
			ncpy(bf2,bf,200);
			s = strchr(bf2,' ');
			if (s!=NULL) *s = 0;
                        if (!chan_canreadgrp(chan,bf2)) continue;
                        strcat(bf,"\r\n");
                        chan_write_s(chan,bf);
                }
                fclosep(f);
                /* ending dot is sent at end of if's */
        } else if (strcmp(bf,"overview.fmt")==0) {
                if (config_true(C_XOVER_DISABLE)) {
                        chan_write_s(chan,"501 command syntax error (or un-implemented option)\015\012");
                        return TRUE;
                }
                chan_write_s(chan,"215 information follows\015\012");
                item_xover_fmt(chan);
                return TRUE;
        } else if (strcmp(bf,"subscriptions")==0) {
                flist = s_alloc();
                s_read_max(flist,DNEWS_SUBSCRIPTIONS,3000);
                if (s_len(flist)==0) {
                        lprintf(bf,BFSZ-1,"503 list not available {%s} \015\012",DNEWS_SUBSCRIPTIONS);
                        chan_write_s(chan,bf);
                        return TRUE;
                
                }
                chan_write_s(chan,"215 list of default group subscriptions follow\015\012");
                for (i=0; i<s_len(flist); i++) {
                        lprintf(bf,BFSZ-1,"%s\015\012",s_line(flist,i));
                        chan_write_s(chan,bf);
                }
                s_free(flist);
        } else if (strcmp(bf,"newsgroups")==0) {
                if (group_n()==0) goto not_available;
                chan_write_s(chan,"215 list of newsgroup names follow\015\012");
                cmd_complete(chan,0,0,0,0,M_LIST_NAMES);
                ncmd_list_names_finish(chan);
                return TRUE;
/*
                for (i=0; i<group_names_n(); i++) {
                        lprintf(bf,BFSZ-1,"%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;
}
#ifdef NETWARE
extern int timezone;
#endif
#ifdef NOTIMEZONE
#define timezone timezonevar
static int timezone;

#endif
int gmt_to_local(int t) /* s = yymmddhhmmss */
{
#ifdef VMS  /* Need to implement this properly*/
        return t;
#else
#ifdef Mac
        return t;
#else
#ifdef USEGMTOFF
        time_t tt;
	struct tm atm;
        struct tm *m; 
        tt = time(NULL);
        m = safe_localtime(&tt,&atm);
        return t - m->tm_gmtoff;
#else
        tzset();
        return t-timezone;
#endif
#endif 
#endif
}

int nntp_time(char *s, int isgmt) /* s = yymmddhhmmss */
{
        char syear[10],smonth[10],sday[10],shour[10],sminute[10],ssecond[10];
        struct tm t,*pt;
	int len;
	int add=0;
	struct tm atm;
	int y;
	int r;
	time_t tt;
	len = strlen(s);
        memset(&t,0,sizeof(t));


	imsgf("nntp_time (%.80s) len=%d\n",s,len);
	emsg_flush();

/*
05 14:56:34 0:info: cmd: Do cmdx[0:10.0.0.15] {NEWNEWS alt.binaries.pictures.boys.retromod 700101 000000}
05 14:56:34 0:info: nntp_time (700101000000) len=12
05 14:56:34 0:info: Date day=01 mon=01 year=70
05 14:56:34 0:info: Date: Invalid date
*/	
	

	tt = time(NULL);
/*	if (isgmt) pt = gmtime(&tt);
	else pt = localtime(&tt);
	
	Changed by chrisp to fix est/edt on linux during daylight saving it was out
	by one hour, changing to localtime() all the time fixed it. 
	
	*/
	pt = safe_localtime(&tt,&atm);
	if (pt!=NULL) t = *pt;
//	t.tm_isdst = 0;
	if (len==13) {
		add=1;
	        ncpy(syear,s,3);
	} else if (len==14) {
	        ncpy(syear,s,4);
		add = 2;
	} else {
		add = 0;
	        ncpy(syear,s,2);
	}
	ncpy(smonth,s+2+add,2);
        ncpy(sday,s+4+add,2);
        ncpy(shour,s+6+add,2);
        ncpy(sminute,s+8+add,2);
        ncpy(ssecond,s+10+add,2);

        y = atoi(syear);
	if (y<50) y += 100;
	if (y>200) y -= 1900;
	if (y<71) return 0;

        t.tm_year = y;
        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);
	imsgf("Date day=%02d mon=%02d year=%02d\n",t.tm_mday,t.tm_mon+1,t.tm_year);

	if (t.tm_year>140) return 0;
	if (t.tm_year<0) return 0;
	if (t.tm_hour>26) return 0;
	if (t.tm_hour<0) return 0;
	if (t.tm_min>61) return 0;
	if (t.tm_min<0) return 0;
	if (t.tm_sec>61) return 0;
	if (t.tm_sec<0) return 0;

#ifdef VAX
#define mktime mymktime
#endif
#ifdef VMS
	r = mktime(&t);
#else
        r = lib_fixtime(mktime(&t));
#endif
	if (isgmt) {
		r += atoi(config_get(C_GMT_FIX));
	}
	if (isgmt) return lib_gmt_to_local(r);
	return r;
}
int ncmd_newgroups(int chan, char *in)  /* newgroups date time GMT <dist> */
{
        char dtime[BFSZ],dist[BFSZ];
        int gmt = FALSE;
        int date;
	char *s;
        int xfrom;
	char tmpstr[BFSZ];
	char bf[BFSZ];
                
        ncpy(dtime,get_word(in,1),100);
        strcat(dtime,get_word(in,2));
        if (strcmp("GMT",get_word(in,3))==0) gmt = TRUE;
        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);

        busy_str1[chan] = str_new();
        str_copysn(busy_str1[chan],dist);

        date = nntp_time(dtime,gmt);

	s = safe_ctime(&date,tmpstr);
	if (s!=NULL) {
		ncpy(bf,s,24);
		imsg("Date: %s\n",bf);
	} else imsg("Date: Invalid Time\n");
        xfrom = group_times_after(date);
        if (xfrom>=group_times_n()) {
                chan_write_s(chan,"231 list of new newsgroups follows\015\012.\015\012");
                str_free(busy_str1[chan]);  busy_str1[chan] = NULL;
                return TRUE;
        }
        chan_write_s(chan,"231 list of new newsgroups follows\015\012");
        cmd_complete(chan,0,0,xfrom,group_times_n(),M_NEWGROUPS);
        ncmd_newgroups_finish(chan);
        return TRUE;
}
void ncmd_newgroups_finish(int chan)
{
        int i;
        int g;
        int nsent=0;
        char dist[BFSZ];

	if (busy_str1[chan]==NULL) {
		emsg("newgroups_finish failed as it's null\n");
                chan_write_s(chan,".\015\012");
                nntp_setbusy(chan,FALSE);       
		return; 
	}
        ncpy(dist,str_s(busy_str1[chan]),BFSZ-1);
        for (i=busy_from[chan]; i<group_times_n() && nsent<500; i++) {
                g = group_times_g(i);
		if (g<0) continue;
		if (strlen(group_name(g))==0) continue;
                if (dist[0]!=0) { 
                        if (dist_match(dist,group_namelc(g))) {
 	                  if (config_true(C_AUTH_SHOWPROT)) goto xx;
                          if (chan_canreadgrp(chan,group_name(g))) {
xx:                             chan_write_s(chan,group_getlist(g));
                                nsent++;
                          }
                        }
                } else {
	          if (config_true(C_AUTH_SHOWPROT)) goto xx2;
                  if (chan_canreadgrp(chan,group_name(g))) {
xx2:                    chan_write_s(chan,group_getlist(g));
                        nsent++;
                  }
                }
        }
        busy_from[chan] = i;
        if (i>=group_times_n() ) {
                chan_write_s(chan,".\015\012");
                nntp_setbusy(chan,FALSE);       
                str_free(busy_str1[chan]);  busy_str1[chan] = NULL;
        }
}


int dist_match(char *dist, char *group)
{
        char bf[BFSZ];
        char *s;
        if (strlen(dist)==0) return TRUE;
        ncpy(bf,group,BFSZ-1);
        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 *gl;
        int r;
        char *s;

        gl = mystrdup(glist,SD_NCMD);
        if (gl==NULL) { emsg("Fatal: Memory allocation failure in matchlist\n"); return FALSE;} 
        s = strtok(gl,",");
        for (;s!=NULL;) {
                if (dist_match(dist,s)) { r=TRUE; goto doreturn;} 
                s = strtok(NULL,",");
        }
        r = FALSE;
doreturn:;
        mystrfree(gl,SD_NCMD);
        return r;
}
/* newnews newsgroups,...  yymmdd hhmmss ["GMT"] [<distributions>] */
/* Too slow, changing this so it only works by the information in the history file */
/* I don't understand what the distributions selection is supposed to do
here, if it is supposed to check the distribution in the item, then that would
cripple the command (and did), otherwise it has no function, so it shouldn't
be used anyway, idiots probably just put it on the definition without thinking
it thru.
*/
void ncmd_newnews_finish(int chan, char *h)
{
        
        int i;
        int nsent=0;
        int k=0;
        int start=lib_gmtime();
        char bf[BFSZ];
        char *s;
        char mid[BFSZ];
        char wildgrps[4000];

        
        ncpy(wildgrps,str_s(busy_str1[chan]),4000-1);

        dmsg("ncmd: newnews from (%d) \n",busy_from[chan]);
        if (h==NULL) {
#ifdef HIST64
		h_both b;
		b.part[0] = (unsigned int) busy_from[chan];
		b.part[1] = (unsigned int) busy_to[chan];
		h = hist_line(b.whole);
#else
		h = hist_line((unsigned int) busy_from[chan]);
#endif
	}
        for (; (h!=NULL) && (nsent<6000) ;k++ ) { /* Increase to 500 */
                if ((k%500)==0) if ((lib_gmtime()-start)>1) break;
                ncpy(mid,h,BFSZ-1);
                s = strchr(mid,'\t'); if (s!=NULL) *s = 0;
                s = hist_groupfirst(h);
                for (i=0;s!=NULL;i++) {
                        if (group_match(wildgrps,s)) {  
                                lprintf(bf,BFSZ-1,"%s\015\012",mid);
                                chan_write_s(chan,bf);
                                nsent++;
                                break;
                        }
                        s = hist_groupnext();
                }
                nsent++;
                h = hist_next();                        
        }
        if (h!=NULL) {
                /* copy_mid(upto,h); would need one for each channel */
#ifdef HIST64
		h_both b;
		b.whole = hist_upto();
                busy_from[chan] = (int) b.part[0];
                busy_to[chan] = (int) b.part[1];
#else
		busy_from[chan] = (int) hist_upto();
#endif
                return ;
        }
        chan_write_s(chan,".\015\012");
        nntp_setbusy(chan,FALSE);       
        str_free(busy_str1[chan]); busy_str1[chan] = NULL;
        if (is_slave()) hist_close();
        return ;
}

void ncmd_feed_refeed(int chan, char *feedname)
{
        char *h;
        static char *groups[100];
	static int groupid[100];
        int nsent=0;
        int i;
        int num;
        char agroup[BFSZ];
        char bf[BFSZ];
        char mid[BFSZ];
        int f;
        char *s;

        chan_write_s(chan,"500 Warning: this command locks up the server for several minutes\015\012");
        f = feed_find(feedname);
        if (f<0) {
                lprintf(bf,BFSZ-1,"500 Feed (%s) not found\015\012",feedname);
                chan_write_s(chan,bf);
                goto done;      
        }

        h = hist_line(0);  /* Finds first non expired item after 'date' */
        if (h==NULL) goto done;
        
        for (; h!=NULL; ) {
                ncpy(mid,h,BFSZ-1);
                s = strchr(mid,'\t'); if (s!=NULL) *s = 0;
  
                s = hist_groupfirst(h);
                if (s==NULL) goto cont;
                ncpy(agroup,s,BFSZ-50);
                num = hist_groupnextnum();

                
                s = hist_groupfirst(h); 
                for (i=0;(s!=NULL) && (i<20);i++) {
                        if (groups[i]==NULL) groups[i] = mymalloc(101);
                        ncpy(groups[i],s,99);
                        s = hist_groupnext();
                }
                groups[i] = NULL;
		for (i=0; groups[i]!=NULL; i++) {
			groupid[i] = group_find(groups[i]);
		}
                if (feed_test(f,mid,"",groups,groupid,"",1000,NULL)) {
                        lprintf(bf,BFSZ-1,"%s:%d",agroup,num);
                        feed_send(f,mid,bf,0);
                        nsent++;
                }
cont:
                h = hist_next();                        
        }
done:
        lprintf(bf,BFSZ-1,"%d articles sent to feed\015\012",nsent);
        chan_write_s(chan,bf);
        chan_write_s(chan,".\015\012");
}
 
int ncmd_newnews(int chan, char *in)
{
	char tmpstr[BFSZ];
        char dtime[BFSZ],dist[BFSZ],wildgrps[4000];
        int i;
        int gmt = FALSE;
        char tmp[BFSZ];
        char bf[BFSZ];
	char tbf[BFSZ];
        int g,ng;
        char *p[400];
        int date;
        char *h;
	char *d;
        char xwild[BFSZ];
        char xwildgrps[4000];     

	
        if ( config_true(C_NO_NEWNEWS)) {
                chan_write_s(chan,"230 newnews disabled by admin, empty list follows \015\012");
                chan_write_s(chan,".\015\012");
                return TRUE;            
        }
/*
	f = fopen(file_log("newnews.log"),"a");
	if (f!=NULL) {
		fprintf(f,"%s\n",in);
		fclose(f);
	}
*/
        ncpy(xwild,config_get(C_NEWNEWS_USERS),BFSZ-1);
        if (strlen(xwild)>0) {
                if (group_match(xwild,chan_getipname(chan)) || group_match(xwild,chan_getipnum(chan))) {
                        goto isok;
                }
                lprintf(bf,BFSZ-1,"230 newnews disabled by admin for (%s) \015\012",chan_getipname(chan));
                chan_write_s(chan,bf);
                chan_write_s(chan,".\015\012");
                return TRUE;            
        }
isok:

        ncpy(wildgrps,get_wordv(in,1),3000);
        ncpy(dtime,get_wordv(in,2),100);
        ncpy(tmp,get_wordv(in,3),10);
        strcat(dtime,tmp);

        if (config_true(C_CACHE_ONNEWNEWS)) {
                if (strlen(wildgrps)>1) {
                 strcpy(xwildgrps,wildgrps); strlwr(xwildgrps);
                 ng = token_split(xwildgrps,p,",*",199);
                 for (i=0; i<ng; i++) {
                        g = group_find(p[i]);
                        if (g>=0) {
                                group_setlife(g,10);
                                group_setcached(g,TRUE);
                        }
                 }
                }
        }       

        busy_str1[chan] = str_new();
        str_copysn(busy_str1[chan],wildgrps);

        if (strcmp("GMT",get_wordv(in,4))==0) gmt = TRUE;
        if (strcmp("gmt",get_wordv(in,4))==0) gmt = TRUE;
        if (gmt) ncpy(dist,get_wordv(in,5),500);
        else ncpy(dist,get_wordv(in,4),500);

        date = nntp_time(dtime,gmt);                
	
	d = safe_ctime(&date,tmpstr);
	if (d!=NULL) {
		ncpy(tbf,d,24); 
		imsg("Date: %s (%d)\n",tbf,date);
	} else {
		imsg("Date: Invalid date\n");
	}


        chan_write_s(chan,"230 list of new articles by message-id follows\015\012");

        /* <msgid> \t date-a~date-e~date-c \t comp.os.vms/1104 alt.x/12  */
        imsg("Newnews, date=%d\n",date);
	hist_cache_flush();
        h = hist_after(date);  /* Finds first non expired item after 'date' */
        if (h==NULL) {
                chan_write_s(chan,".\015\012");
                str_free(busy_str1[chan]); busy_str1[chan] = NULL;
                return TRUE;
        }
#ifdef HIST64
	{
		h_both b;
		b.whole = hist_upto();
	        cmd_complete(chan,0,0,(int) b.part[0],(int) b.part[1],M_NEWNEWS);
	}
#else
        cmd_complete(chan,0,0,hist_upto(),0,M_NEWNEWS);
#endif
        ncmd_newnews_finish(chan,h);
        return TRUE;
}                               
int ncmd_newnews_24(int chan, char *in)
{
        char dtime[BFSZ],dist[BFSZ],bf[BFSZ],wildgrps[BFSZ];
        char mid[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,gmt);                

        chan_write_s(chan,"230 list of new articles by message-id follows\015\012");
        /* <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' */
        if (h==NULL) {
                chan_write_s(chan,".\015\012");
                return TRUE;
        }
        
        for (; h!=NULL; ) {
                ncpy(mid,h,BFSZ-1);
                s = strchr(mid,'\t'); if (s!=NULL) *s = 0;
                s = hist_groupfirst(h);
                for (i=0;s!=NULL;i++) {
                        if (group_match(wildgrps,s)) {  
                                lprintf(bf,BFSZ-1,"%s\015\012",mid);
                                chan_write_s(chan,bf);
                                break;
                        }
                        s = hist_groupnext();
                }
                h = hist_next();                        
        }
        chan_write_s(chan,".\015\012");
        return TRUE;
}                               
int ncmd_newnews3(int chan,char *dist, int g, int num)
{
        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) {
                                lprintf(bf,BFSZ-1,"%s\015\012",s);
                                chan_write_s(chan,bf);
                                return TRUE;
                        }
                }
                return FALSE;
        }
        return FALSE;
}
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 */
        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) {
                                lprintf(bf,BFSZ-1,"%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;
	static int x;
	x++;
	seq_next(chan,S_NEXT,FALSE);

	if (config_true(C_NEXT_BUG)) {
	 if ((x%7)==3) {
		chan_write_s(chan,"423 no next article in this group\015\012");
		return FALSE;
	 }
	}
        /* Search forwards for valid article number, only return status info. */
        if (!ncmd_getcur(chan,in,&grp,&num)) return FALSE;
        last = group_getend(grp);
        if (num==last) goto skipit;
        num = g_find_next(grp,num);
        for (;(num<=last) && (num>0);) {
                head = item_head(grp,num);
                if (head!=NULL) break;
                num = g_find_next(grp,num);
        }
        if (head==NULL) {
skipit:         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:");
        lprintf(bf,BFSZ-1,"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[BFSZ];
	char bf2[BFSZ];
        int delay;
        ihavein_clear(chan);
        big_init(chan);
        chan_postchan[chan] = -1;

        if (ispause) {
                lprintf(bf,BFSZ-1,"441 Sorry Paused, try later (%s) \015\012",pause_reason);
                chan_write_s(chan,bf);
                return TRUE;
        }

        if (!chan_canpost(chan)) {
                chan_write_s(chan,"440 posting not allowed\015\012");
        } else {
                chan_setstate(chan,C_POST);
		lprintf(bf2,BFSZ-1,"340 Ok, recommended ID %s\r\n",lib_messageid());
                chan_write_s(chan,bf2);
		if (spam_hostok(chan)) {
			imsg("User matched spam_allowhosts so spam_npost doesn't apply\n");
			return TRUE;
		}
                delay = spam_postdelay(chan_getipnum(chan));
                if (delay>0) {
                        /* Block this channel for 2^n seconds */
                        cmd_complete(chan,lib_gmtime() + delay,0,0,0,M_XDELAY);
                        return TRUE;
                }
        }
        return TRUE;
}
int spam_hostok(int chan)
{
	if (strlen(config_get(C_SPAM_ALLOWHOSTS))==0) return FALSE; 
	if (group_match(config_get(C_SPAM_ALLOWHOSTS),chan_getipnum(chan))) return TRUE;
	if (group_match(config_get(C_SPAM_ALLOWHOSTS),chan_getipname(chan))) return TRUE;
	return FALSE;

}
int ncmd_getpost(int chan, char *in, int sz)
{
        char bf[BFSZ];
        String *get;
        char toslave[BFSZ];
        int pchan = chan_postchan[chan];
 

        if (strcmp(in,".\015\012")==0) {

                if (pchan>=0) {
                        imsg("post: Got post from slave\n");
                        lprintf(toslave,BFSZ-1,"xdpost %d ",pchan);
                        chan_postchan[chan] = -1;
                } else strcpy(toslave,"");

                imsg("Processing local post\n");
                chan_setstate(chan,C_CMD);

		if (strlen(toslave)==0) if (atoi(config_get(C_SPAM_NPOST_MAX))>0) {
			int n;
			n = pf_addone(chan_getipnum(chan),0);
			if (n>atoi(config_get(C_SPAM_NPOST_MAX))) {
				emsg("Silently rejecting post as spam_npost_max exceeded %d posts from %s\n",n,chan_getipnum(chan));
		                chan_write_s(chan,"240 article posted ok\015\012");
				big_free(chan);
				return TRUE;
			}
		}
		if (strlen(toslave)==0) {
			int n;
			int max;

			max = atoi(config_get(C_SPAM_USER_NPOST_MAX));
			if (strlen(chan_getuser(chan))>0) if (max>0) {
				n = pf_addone(chan_getuser(chan),1);
				imsg("spam_user_npost_max %d %d %s\n",n,max,chan_getuser(chan));
				if (n>max) {
					emsg("Silently rejecting post as spam_user_npost_max exceeded %d posts from %s\n",n,chan_getuser(chan));
			                chan_write_s(chan,"240 article posted ok\015\012");
					big_free(chan);
					return TRUE;
				}
			}
		}
	     

                if (big_len(chan)==0) {
                        lprintf(bf,BFSZ-1,"%s441 Article is empty?\015\012",toslave);
                        msg_reject("%s",bf);
                        chan_write_s(chan,bf);
                } else if (ihavein_failed[chan]) {
                        ihavein_failed[chan] = FALSE;
                        lprintf(bf,BFSZ-1,"%s441 Articles may not be larger than %d bytes\015\012",toslave,MAX_STRLEN);
                        msg_reject("%s",bf);
                        chan_write_s(chan,bf);
                } else {
                        get = big_get(chan);
                        if (get==NULL) {
                                lprintf(bf,BFSZ-1,"%s441 Temporary store failed or item too large\015\012",toslave);
                                msg_reject("%s",bf);
                                chan_write_s(chan,bf);  
                        } else {
                                post_article(chan,get,toslave);
				ncmd_pipe(chan,str_len(get),"post");
                                feed_flush();
                                if (!config_true(C_POST_NOSUCK)) {
                                        if (chan_getgrp(chan)>=0) suck_que_agroup(chan_getgrp(chan));
                                }
                        }
                }               
                /* Trigger a reload of that group */
                big_free(chan);
        } else {
                /* show_outbf(in,sz);*/
                if (big_len(chan) >= MAX_STRLEN) ihavein_failed[chan] = TRUE;
                else big_add(chan,in,sz);
        }
        return TRUE;
}

int ncmd_iswronghost(int chan)
{
	char bf[BFSZ];
	if (chan_wronghost[chan]!=NULL) {
		imsg("wide: Logging user off [%d] %s\n",chan,chan_getuser(chan));
		lprintf(bf,BFSZ-1,"505 Already connected on host (%.100s)\015\012",chan_wronghost[chan]);
	        chan_write_s(chan,bf);
	        ifstrfree(chan_wronghost[chan]);
		chan_wronghost[chan] = NULL;
	        chan_close(chan); /* Might need to set a state to allow time to close*/
	        chan_authcnt[chan] = 0;
		return TRUE;
	}	
        return FALSE;
}
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*/
        chan_authcnt[chan] = 0;
        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;
        int grp,num;
        char *mid,bf[BFSZ];     

	seq_next(chan,S_STAT,strlen(in)>4);

        if (is_slave()) {
          ncpy(bf,get_word(in,1),BFSZ-1);
          if (bf[0]=='<') { slave_lookup(chan,bf,SL_STAT); return TRUE; } 
        }

	if (config_true(C_STAT_BUG)) {
		imsg("resetting counter due to stat_bug\n");
                chan_setnum(chan,group_getstart(chan_getgrp(chan)));
	}

        
	if (!ncmd_findart(chan,in,&grp,&num)) return FALSE;

        if (db_iscancel(grp,num)) {
                imsg("item cancelled %s:%d\n",group_name(grp),num);
                goto gone;
        }
        head = item_head(grp,num);
        if (head==NULL) {
gone:           chan_write_s(chan,art_fail);
                return FALSE;
        }
        if (config_true(C_INN_BUG)) {
          if ((num%4) || ((num>100) && (num<200) ) ) {  
                chan_write_s(chan,"430 no such article found\015\012");
                chan_setnum(chan,group_getstart(grp));
                emsg("Dummy gaps for testing %d, current now %d\n",num,group_getstart(grp));
                return FALSE;
          }
        }
        mid = get_ahead(head,"Message-ID:");
        lprintf(bf,BFSZ-1,"223 %d %s article retrieved - request text separately\015\012",num,mid);
        chan_write_s(chan,bf);

        return TRUE;
}
int ncmd_xdomain(int chan, char *in)
{
	char bf[BFSZ];
	lprintf(bf,200,"500 Domain (%s) (%s)\r\n",dom_ini_get(C_ORG),dom_ini_get(C_MYNAME));
        chan_write_s(chan,bf);
        return TRUE;
}
int ncmd_mode(int chan, char *in)
{
        
        char p1[BFSZ];
	int status;
	char *canpost;
	char bf[BFSZ];
        ncpy(p1,get_word(in,1),200);
        strlwr(p1);

        if (strcmp(p1,"reader")==0) {
		seq_next(chan,S_MODE,TRUE);


		status = 201;
	        canpost = "posting disabled";
		if (chan_canpost(chan)) {canpost = "posting OK"; status=200;}
		
		if (strlen(config_get(C_WELCOME))>0) {
		  lprintf(bf,BFSZ-1,"%d %s\r\n",status,config_get(C_WELCOME));
                  chan_write_s(chan,bf);
		} else {
                  lprintf(bf,BFSZ-1,"%d DNews is in reader mode - %s.\015\012",status,canpost);
                  chan_write_s(chan,bf);
		}
        } else if (strcmp(p1,"stream")==0) {
                if (config_true(C_NOSTREAM)) {
                        chan_write_s(chan,"500 Syntax error or bad command\015\012");
                } else { 
                        chan_write_s(chan,"203 StreamOK\015\012");
                }
        } else if (strcmp(p1,"compress")==0) {
#ifdef DOCOMPRESS
		if (config_true(C_COMPRESS_DISABLE)) {
			chan_write_s(chan,"500 mode compress disabled sorry in dnews.conf\015\012");
		} else {
			chan_write_s(chan,"207 Compression enabled\015\012");
			chan_setcompress(chan);
		}
#else
		chan_write_s(chan,"500 mode compress only supported on other DNEWS platforms sorry\015\012");
#endif
        } else {
                chan_write_s(chan,"500 Syntax error or bad command\015\012");
        }
        return TRUE;
}
/* authinfo user Name  */
/* authinfo pass Password */
char *chan_reason(void);
int ncmd_authinfo(int chan, char *in)
{
        int n,max;
	char tmp[BFSZ];
        char p1[BFSZ];
	char bf[BFSZ];
	char *s;
	char p2[BFSZ];
	int failtime;
        ncpy(p1,get_word(in,1),200);
        strlwr(p1);
        if (strcmp(p1,"user")==0) {
		user_record(chan);
		if (config_true(C_LOG_USERS)) wmsg("users: %s authinfo user %s\n",chan_getipnum(chan),p2);
		if (config_true(C_AUTH_SPACES)) ncpy(p2,get_rest(in,2),99);
		else ncpy(p2,get_word(in,2),99);
                imsg("cmd: authinfo user %s\n",p2);
                chan_write_s(chan,"381 PASS required\015\012");
                chan_setuser(chan,p2);
		if (config_true(C_APPROVE_AUTHORIZED)) {
			chan_setauth(chan,FALSE);
		}
		if (config_true(C_AUTHINFO_STRICT)) {
			chan_setauth(chan,FALSE);
		}
		if (config_true(C_AUTH_USERDOMAIN)) {
			ncpy(bf,chan_getipnum(chan),100);
			if (strchr(bf,'@')==NULL) {
				s = strchr(p2,'@');
				if (s!=NULL) {
					lprintf(tmp,BFSZ-1,"%.100s@%.100s",bf,s+1);
					imsg("Using users domain for user (%s) ip (%s)\n",p2,tmp);
					chan_setipnum(chan,tmp);
				}
			}
		}
        } else if (strcmp(p1,"pass")==0) {
		if (pass_fail_recent(chan_getipnum(chan))) {
			imsg("pass: too many attempts from (%s %.80s)\n",chan_getipnum(chan),chan_getuser(chan));
			failtime = atoi(config_get(C_PASS_TIMEOUT));
			if (failtime==0) failtime = 4;
                        lprintf(bf,BFSZ-1,"502 Too many attempts in %d seconds - try later\015\012",failtime);
                        chan_write_s(chan,bf);
                        return TRUE;
		}
		if (config_true(C_AUTH_SPACES)) ncpy(p2,get_rest(in,2),99);
		else ncpy(p2,get_word(in,2),BFSZ-1);
                imsg("cmd: authinfo pass xxxxxx\n");
                if (strcmp(chan_getuser(chan),"")==0) {
                        chan_write_s(chan,"482 USER required\015\012");
                        return TRUE;
                }
		chan_setpass(chan,p2); /* Only used in dll_readart */
                max = atoi(config_get(C_AUTH_LOGIN_MAX));
                if (max>0) {
                        n = chan_nloggedin(chan_getuser(chan));
                        if (n>max) {
                                chan_write_s(chan,"502 Authentication error, you are already connected too many times\015\012");
                                return TRUE;
                        }
                }
                if (config_true(C_USER_SINGLE)) {
                        if (chan_otheruser(chan)) {
                                chan_write_s(chan,"502 Authentication error, you are already connected from another address\015\012");
                                return TRUE;
                        }
                }
                if (strlen(config_get(C_AUTH_POPHOST))>0) {
			if (config_true(C_AUTH_LOCAL_TOO)) {
		                if (chan_check(chan,p2)) goto userisok;
			}
                        pop_quelookup(chan,chan_getuser(chan),p2);
                        return TRUE;
                }
                if (chan_check(chan,p2)) {
			if (config_true(C_LOG_USERS)) wmsg("users: %s logged in ok\n",chan_getipnum(chan));
			if (strlen(config_get(C_AUTH_MASK))>0) {
				if (!group_match(config_get(C_AUTH_MASK),chan_getipnum(chan))) {
					lprintf(bf,100,"502 Login from AUTH_MASK ip address only not (%s)\r\n",chan_getipnum(chan));
					imsg("502 Login from AUTH_MASK ip address only not (%s)\n",chan_getipnum(chan));
					chan_write_s(chan,bf);
					return TRUE;
				}
			}
userisok:               chan_write_s(chan,"281 Ok\015\012");
			ncmd_pipe(chan,0,"login");

                } else {
			if (config_true(C_LOG_USERS)) wmsg("users: %s login failed \n",chan_getipnum(chan));
			if (config_true(C_AUTH_FRIENDLY)) {
				chan_setipname(chan,chan_getipname(chan));
				if (chan_cantalk(chan)) {
		                        chan_write_s(chan,"281 Ok (bad password but ip access allowed)\015\012");			
				}
				return TRUE;
			}
			pass_fail_add(chan_getipnum(chan));
                        lprintf(bf,BFSZ-1,"502 Authentication error %s\015\012",chan_reason());
                        chan_write_s(chan,bf);
                }
        } else {
                chan_write_s(chan,"481 bad authinfo param\015\012");
        }
        return TRUE;
}
int authent_group(int c, int g)
{
        if (g<0) return TRUE;
        if (chan_authgrp[c]==g) return TRUE;

        if (!chan_canreadgrp(c,group_name(g))) {
                return FALSE;
        }
        chan_authgrp[c] = g;
        return TRUE;
}
int ncmd_date(int chan, char *in)
{
        time_t t;
        struct tm *gmt;
	struct tm atm;
        char bf[200];
        time(&t);
        gmt = safe_gmtime(&t,&atm); 

        /* 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 = safe_localtime(&t,&atm);  

        lprintf(bf,200-1,"111 %4.4d%2.2d%2.2d%2.2d%2.2d%2.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);
/*
        lprintf(bf,200-1,"%2.2d%2.2d%2.2d%2.2d%2.2d%2.2d",
                gmt->tm_year, gmt->tm_mon + 1, gmt->tm_mday,
                gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
        emsg("Time check %d = %d %s\n",lib_gmtime(),nntp_time(bf),bf);
*/
        return TRUE;
}
/* Let's fake this at the moment, I think my system is fast enough */
/* xover [range]                                                   */
int ncmd_xover_old(int chan, char *in)
{
        char range[BFSZ];
        int from,to,i,grp;
        String *ss;
        
        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 */
                ss = item_xover(grp,i,i!=from);
                if (ss!=NULL) {
                        chan_write_buff_str(chan,ss);
                }
        }
        chan_write_flush(chan); 
        chan_write_s(chan,".\015\012");
        return TRUE;
}

int ncmd_xover(int chan, char *in)
{
        char range[BFSZ];
        int from,to,grp;
        int r;
	

        if (config_true(C_XOVER_DISABLE)) {
                chan_write_s(chan,"500 command not recognized\015\012");
                return TRUE;
        }

	seq_next(chan,S_XOVER,strlen(in)>5);
        busy_search[chan] = NULL;
       
        ncpy(range,get_word(in,1),299);
        if (!ncmd_range(chan,range,&grp,&from,&to)) return FALSE;
        if (!authent_group(chan,chan_getgrp(chan))) {
                auth_deny(chan);
                return FALSE;
        }

        
        nntp_setbusy(chan,TRUE);
        cmd_mode[chan] = M_XOVER;
        chan_write_s(chan,"224 data follows\015\012");
        busy_from[chan] = from;
        busy_to[chan] = to;
        busy_p1[chan] = grp;
        stat_start(X_XOVER);
        r = xover_finish(chan);
        stat_end(X_XOVER);
        if (config_true(C_CACHE_ONXOVER)) {
          if (!group_iscached(grp)) {
                if (is_slave()) group_setcached_slave(grp);
                else {
                  if (!group_inupdate(grp)) {
                        suck_queue_anyone(group_name(grp),0);
                  }
                  group_setupdate(grp,TRUE);
                }
          }
          if (!skip_used) group_used(chan,grp,from,0);  /* Log use of article by that user */
        }
        return r;
}
int ncmd_xdfind(int chan, char *in)
{
        char range[BFSZ];
        int from,to,grp;

        ncpy(range,get_word(in,1),299);
        if (!ncmd_range(chan,range,&grp,&from,&to)) return FALSE;
        nntp_setbusy(chan,TRUE);
        cmd_mode[chan] = M_XOVER;
        busy_search[chan] = mystrdup(in,SD_NCMD);
        chan_write_s(chan,"224 data follows\015\012");
        busy_from[chan] = from;
        busy_to[chan] = to;
        busy_p1[chan] = grp;

        return xover_finish(chan);
}



#define MAXFIND 10

int cmd_find(String *xov, char *xfind[], int nfind)
{
        char bf[BFSZ];
        int i;
        ncpy(bf,str_s(xov),BFSZ-10);
        strlwr(bf);
        for (i=0; i<nfind; i++) {
                if (strstr(bf,xfind[i])!=NULL) return TRUE;
        }
        return FALSE;
}
int nntp_set_busy(int c);
/* nntp_outsz(c) */
int xover_finish(int chan)
{
        int from = busy_from[chan];     
        int to = busy_to[chan]; 
        int grp = busy_p1[chan];        
        int i;
	int r;
	int maxout = chan_outspeed(chan);
	char reason[BFSZ];
        int isfind = FALSE;
        char bf[BFSZ];
        int n;
        String *ss;
        static char *xfind[MAXFIND];
        char *s;
	static String *outbf;
        int nfind=0;
	int start_time;

	imsg("xover_finish step 1\n");
	if (maxout>1000000) maxout = 1000000;
	if (outbf==NULL) outbf = str_new();
	str_setlen(outbf,0);
	if (busy_senddot[chan]) goto send_dot;

        if (busy_search[chan]!=NULL) { /* Search xover headers for these things */
                if (xfind[0]==NULL) for (i=0; i<MAXFIND; i++) {
                        xfind[i] = mymalloc(300);
                }
                isfind = TRUE;
                ncpy(bf,busy_search[chan],BFSZ-100);
                s = strtok(bf," \015\012\t");
                if (s!=NULL) s = strtok(NULL," \015\012\t"); 
                if (s!=NULL) s = strtok(NULL," \015\012\t"); 
                nfind = 0;
                for (i=0; (s!=NULL) && (i<MAXFIND); i++) {
                        ncpy(xfind[i],lib_unescape(s),280);
                        strlwr(xfind[i]);
                        nfind = i + 1;
                        s = strtok(NULL," \015\012\t"); if (s==NULL) break;
                }       
        }

	imsg("xover_finish step 1.1\n");
        n = g_list_start(grp);
	imsg("xover_finish step 2\n");
/*        for (;(n<from) && (n>0) ;) n = g_list_next();  find where we are upto */ 
	if (n<from) n = g_list_search(from);
	start_time = lib_gmtime();
	imsg("xover_finish step 3\n");
        for (i=0; (i<4000) && (n>=0) && (n<=to); i++)  {
		if (nntp_outsz(chan)>1000000) break; /* Don't ever send more than 1mb at a time */
		if ((i%200) == 199) {
			if ((lib_gmtime()-start_time)>2) break;
			if (str_len(outbf)>maxout) break;
		}
                if (db_iscancel(grp,n)) goto nextone;
                ss = item_xover(grp,n,i!=0);
		if (i==0) {
			imsg("xover, first %d seconds\n",lib_gmtime()-start_time);
			start_time = lib_gmtime();
		}
                if (ss!=NULL) {
                        if (isfind) {
                                if (cmd_find(ss,xfind,nfind)) {
                                        str_add(outbf,ss);
                                }
                        } else {
                                str_add(outbf,ss);
                                user_addinfo(chan,0,1,str_len(ss));
                        }
                }
nextone:        n = g_list_next();
        }
	imsg("xover_finish step 4, done %d, %d/%d,  %d seconds\n",i,n,to,lib_gmtime()-start_time);
        
        if ((n>=to) || (n<0)) {
		if (config_true(C_CHAN_NOBUFF)) {
			if (str_len(outbf)>0) chan_write(chan,outbf);
			str_clear(outbf);
			busy_senddot[chan] = TRUE;
#ifdef WIN32
			lib_msleep(1);
#endif
			return TRUE;
		}
send_dot:	busy_senddot[chan] = FALSE;
		str_addsn(outbf,".\015\012");
                nntp_setbusy(chan,FALSE);
                if (busy_search[chan] != NULL) mystrfree(busy_search[chan],SD_NCMD);
                busy_search[chan] = NULL;
        }
	chan_write(chan,outbf);
	if (!config_true(C_ML_DISABLE)) {
	 r = ml_add(chan,1,str_len(outbf),reason);
	}
	ncmd_pipe(chan,str_len(outbf),"xover");


	imsg("xover_finish step 9 (%d)\n",str_len(outbf));
	if (str_len(outbf)>200000) str_clear(outbf);
        busy_from[chan] = n;
        busy_to[chan] = to;
        return TRUE;
}
int ncmd_xhdr(int chan, char *in)
{
        char range[BFSZ];
        int from,to,grp;
        char bf[BFSZ];
        char hdr[BFSZ];
        if (config_true(C_XHDR_DISABLE)) {
                chan_write_s(chan,"500 command not recognized\015\012");
                return TRUE;
        }

	seq_next(chan,S_XHDR,strlen(in)>4);

        ncpy(hdr,get_word(in,1),299);
        ncpy(range,get_word(in,2),299);
        grp = -1;
        if (!ncmd_range(chan,range,&grp,&from,&to)) return FALSE;
        if (grp<0) if (chan_getgrp(chan)<0) {
                chan_write_s(chan,"412 no newsgroup has been selected\015\012");
                return FALSE;
        }
        if (!authent_group(chan,chan_getgrp(chan))) {
                auth_deny(chan);
                return FALSE;
        }


        lprintf(bf,BFSZ-1,"221 %s fields follow\015\012",hdr);
        chan_write_s(chan,bf);

        nntp_setbusy(chan,TRUE);
        cmd_mode[chan] = M_XHDR;
        busy_from[chan] = from;
        busy_to[chan] = to;
        busy_p1[chan] = grp;
	if (busy_hdr[chan]!=NULL) mystrfree(busy_hdr[chan],SD_NCMD);
	busy_hdr[chan] = mystrdup(hdr,SD_NCMD);
        return xhdr_finish(chan);
}
int ncmd_xpat(int chan, char *in)
{
        char range[BFSZ];
        int from,to,grp;
        char bf[BFSZ];
        char wildpat[BFSZ];
        char hdr[BFSZ];
        if (config_true(C_XHDR_DISABLE)) {
                chan_write_s(chan,"500 command not recognized\015\012");
                return TRUE;
        }


        ncpy(hdr,get_word(in,1),299);
        ncpy(range,get_word(in,2),299);
        ncpy(wildpat,get_rest(in,3),299);


        grp = -1;
        if (!ncmd_range(chan,range,&grp,&from,&to)) return FALSE;
        if (grp<0) if (chan_getgrp(chan)<0) {
                chan_write_s(chan,"412 no newsgroup has been selected\015\012");
                return FALSE;
        }
        if (!authent_group(chan,chan_getgrp(chan))) {
                auth_deny(chan);
                return FALSE;
        }


        lprintf(bf,BFSZ-1,"221 %s fields follow\015\012",hdr);
        chan_write_s(chan,bf);

        nntp_setbusy(chan,TRUE);
        cmd_mode[chan] = M_XHDR;
        busy_from[chan] = from;
        busy_to[chan] = to;
        busy_p1[chan] = grp;
	if (busy_xpat[chan]!=NULL) mystrfree(busy_xpat[chan],SD_NCMD);
	busy_xpat[chan] = mystrdup(wildpat,SD_NCMD);
	if (busy_hdr[chan]!=NULL) mystrfree(busy_hdr[chan],SD_NCMD);
	busy_hdr[chan] = mystrdup(hdr,SD_NCMD);

        return xhdr_finish(chan);
}
int auth_deny(int chan)
{
	char bf[BFSZ];
	char reason[BFSZ];
	int code = 480;
	strcpy(reason,"Permission denied");
	if (strlen(config_get(C_AUTH_MESSAGE_GROUP))>0) {
		ncpy(reason,config_get(C_AUTH_MESSAGE_GROUP),BFSZ-1);
	}

	imsg("User denied access %s\n",reason);
    if ((chan_authcnt[chan]++)>4) code = 502; else code = 480;
	lprintf(bf,BFSZ-1,"%d %s\r\n",code,reason);		
		
	chan_write_s(chan,bf);
    return TRUE;
}
int xhdr_finish(int chan)
{
        int from = busy_from[chan];     
        int to = busy_to[chan]; 
        int grp = busy_p1[chan];        
        char bf[BFSZ];
        int i;
	char *hdr = busy_hdr[chan];
        char *s;
        int n;
        int isx;

        isx = item_isxover(hdr);
        n = g_list_start(grp);
        for (;(n<from) && (n>0);) n = g_list_next(); /* find where we are upto */ 
        for (i=0; (i<200) && (n>=0) && (n<=to); i++)  {
                if (isx>=0) s = item_getahead_x(grp,n,isx,i!=0);
                else s = item_getahead(grp,n,hdr);
                
                if (s!=NULL) {
                  if (strlen(s)==0) s = "(none)";
                  if (strlen(s)>BFSZ-20) {
                    s[BFSZ-21] = 0;
                    wmsg("ncmd: Header truncated %s %d {%s} \n",group_name(grp),n,s);
                  }
		  if (busy_xpat[chan]!=NULL) if (!match_wild(busy_xpat[chan],s)) goto skipit;   
                  lprintf(bf,BFSZ-1,"%d %s\015\012",n,s);
                  chan_write_buff(chan,bf);
skipit: ;
                }
                n = g_list_next();
        }
        chan_write_flush(chan);

        if ((n>=to)||(n<0)) {
                chan_write_s(chan,".\015\012");
                nntp_setbusy(chan,FALSE);
		if (busy_xpat[chan]!=NULL) mystrfree(busy_xpat[chan],SD_NCMD);
		busy_xpat[chan] = NULL;
		if (busy_hdr[chan]!=NULL) mystrfree(busy_hdr[chan],SD_NCMD);
		busy_hdr[chan] = NULL;
        }
        busy_from[chan] = n;
        busy_to[chan] = to;
        return TRUE;
}
int delay_finish(int chan)
{
        if (lib_gmtime()>busy_p1[chan]) {
                imsg("Posting delay (see spam_npost) finished for channel %d\n",chan);
                nntp_setbusy(chan,FALSE);
        }
        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_old(int chan, char *in)
{
        char hdr[BFSZ],range[BFSZ],wildpat[BFSZ];
        int from,to,i,grp;
        char *s;
        int isx;
        char xhead[BFSZ];
        char bf[BFSZ];
        int n;
                
        ncpy(wildpat,get_rest(in,3),299);
        ncpy(hdr,get_word(in,1),299);
        ncpy(range,get_word(in,2),299);
     
        if (strlen(hdr)==0) {
                chan_write_s(chan,"501 header range|MessageID pat [morepat...]\015\012");
                return FALSE;
        }
        if (!ncmd_range(chan,range,&grp,&from,&to)) return FALSE;
        if (!authent_group(chan,chan_getgrp(chan))) {
                auth_deny(chan);
                return FALSE;
        }

        lprintf(bf,BFSZ-1,"221 %s matches follow\015\012",hdr);
        chan_write_s(chan,bf);

        isx = item_isxover(hdr);
        n = g_list_start(grp);
        for (;(n<from) && (n>0);) n = g_list_next(); /* find where we are upto */ 
        for (i=0; (n>=0) && (n<=to); i++)  {
                if (isx>=0) s = item_getahead_x(grp,n,isx,i!=0);
                else s = item_getahead(grp,n,hdr);
                if (s!=NULL) {
                        if (strlen(s)==0) s = "(none)";
                        ncpy(xhead,s,300);
                        if (match_wild(wildpat,xhead)) {    
                                lprintf(bf,BFSZ-1,"%d %s\015\012",n,xhead);
                                chan_write_buff(chan,bf);
                        }
                }
                n = g_list_next();
        }

        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];
        int i;

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

        chan_write_s(chan,"282 list follows\015\012");
        for (i=0; i<group_n(); i++) if (!group_isdeleted(i))  if (group_gettitle(i)!=NULL)  { 
                if (match_wild(wildpat,group_name(i))) {
                        lprintf(bf,BFSZ-1,"%s\t%s\015\012",group_name(i),group_gettitle(i));
                        chan_write_s(chan,bf);
                }
        }
        chan_write_s(chan,".\015\012");
        return TRUE;
}
char *db_status(void);
char *db_pile_status(void);
char *nntp_status(void);
static double stat_usage;
String *dnews_status(void)
{
        static String *stat;
        char bf[BFSZ];
        if (stat==NULL) stat = str_new();
        lib_checkspace();

        str_copysn(stat,"");
	lprintf(bf,100,"System: %s, ",SYSTEM_TYPE) ;
	str_addsn(stat,bf);
        str_addsn(stat,uptime_status());

        str_add(stat,feed_status());



        str_add(stat,chan_status());
	if (db_ispile()) {
		imsgf("db_pile_status\n");
	        str_addsn(stat,db_pile_status());
		imsgf("db_pile_status_done\n");
		str_addsn(stat,"\r\n");
	}
        str_add(stat,expire_status());
        str_add(stat,suck_status());
        str_addsn(stat,nntp_getstatus());

        str_addsn(stat,db_status());
        str_add(stat,hist_status());
        str_addsn(stat,lib_status());
	str_addsn(stat,bsort_status());
	str_addsn(stat,tomail_status());
	str_addsn(stat,getmail_status());
	if (strlen(bsort_status())>0) 	str_addsn(stat,"\r\n");


/* Bug is below here */
        if (stat_usage > 0) {
                lprintf(bf,BFSZ-1,"Busy %.2g%%\015\012",stat_usage); 
                str_addsn(stat,bf);
        }
        str_addsn(stat,xidle_status());

        str_addsn(stat,group_status());
	str_addsn(stat,filter_status());
        str_addsn(stat,expire_err());
	lprintf(bf,BFSZ-1," Cancel %d, ReadDel %d, Killitem %d, Ndel %d\r\n",cancel_total_get(),readel_total(),kill_total(),item_ndel());
        str_addsn(stat,bf);


        str_addsn(stat,get_recent());
	str_addsn(stat,pull_status());
	str_addsn(stat,part_status());
	imsg("dnews_status 1\n");
	str_addsn(stat,db_rerun_status());
	imsg("dnews_status 2\n");
	if (lib_nthread()+thout_n()>0) {
		lprintf(bf,BFSZ-1,"Input threads running %d, output threads %d\r\n",lib_nthread(),thout_n());
                str_addsn(stat,bf);
	}
	if (nwrong>0) {
		lprintf(bf,BFSZ-1,"Number of incorrect message id's retrieved %d\n",nwrong);
		str_addsn(stat,bf);
	}
	imsg("dnews_status 3\n");

        return stat;
}
#define TEMP_FILE file_work("report.tmp")
void dnews_manager_report(char *subj)
{
        String *xnew;
        char bf[1300],to[BFSZ];

	imsg("Generating dnews manager report (%s)\n",subj);

        if (strstr(subj,"Pausing")!=NULL) goto contin;
        if (strstr(subj,"Starting")!=NULL) goto contin;
        
        ncpy(bf,config_get(C_REPORT),100);
        strlwr(bf); if (strstr(bf,"no")!=NULL) {
		imsg("report: disabled in dnews.conf, set report true to enable\n");
		return;
	}
        strlwr(bf); if (strstr(bf,"false")!=NULL) {
		imsg("report: disabled in dnews.conf, set report true to enable\n");
		return;
	}
contin:
        imsg("Generating Manager Report {%s}\n",subj);
        ncpy(to,config_get(C_MANAGER),100);

        xnew = str_new();
        lprintf(bf,1300-1,"From: %s@%s\015\012",DNEWS_NAME,config_get(C_MYNAME)); str_copysn(xnew,bf);
        if (strlen(config_get(C_MAIL_FROM))>0) {
          lprintf(bf,1300-1,"From: %s\015\012",config_get(C_MAIL_FROM)); str_copysn(xnew,bf);
        }
        lprintf(bf,1300-1,"Subject: %s\015\012",subj); str_addsn(xnew,bf);
        lprintf(bf,1300-1,"To: %s\015\012",to); str_addsn(xnew,bf);
        lprintf(bf,1300-1,"Date: %s\015\012",lib_email_date()); str_addsn(xnew,bf);
        lprintf(bf,1300-1,"Message-ID: %s\015\012",lib_messageid()); str_addsn(xnew,bf);

        str_addsn(xnew,"\015\012");

        lprintf(bf,1300-1, "%s Version %s, %s \015\012",
               config_get(C_MYNAME),DNEWS_NAME,DNEWS_VERSION);
        str_addsn(xnew,bf);

	imsg("Status step 2\n");
        dnews_getkey(bf);
        if (strlen(bf)!=0) {
                str_addsn(xnew,bf);
                str_addsn(xnew,"\r\n");
        }
        str_add(xnew,dnews_status());
	imsg("Status step 3\n");
	str_addsn(xnew,"\r\n");
        str_add(xnew,stats_show_in());
	str_addsn(xnew,"\r\n");
        str_add(xnew,feed_out());
	imsg("Status step 4\n");
        str_add(xnew,stats_groups_read("",-1));
	imsg("Status step 5\n");
	if (config_true(C_REPORT_BUCKETS)) {
	        str_add(xnew,db_showdbn());
	}
        remove(TEMP_FILE);
        str_addfile(xnew,SPAM_BRIEF);
	remove(SPAM_BRIEF);
	imsg("Status step 6\n");
        if (!str_write(xnew,TEMP_FILE)) filemsg("Error writing %s \n",TEMP_FILE); 
	imsg("Status step 7\n");
	if (!lib_mail_file2(to,subj,TEMP_FILE)) {
                emsg("dnews: Failed to email report to manager\n");
        }
	imsg("Status step 8\n");
        str_free(xnew);
        imsg("Report finished\n");
}

void stats_idle1(int st, int t);
void stats_idle2(void);

static char scur_cmd[BFSZ];
static int istiming;
void ncmd_settiming(int x)
{
        istiming = x;
}
int stats_clock(void)
{
        static int ck;
        if (istiming) return my_clock();
        return ck++;
}
#define MAX_STAT 100
static int x_time[MAX_STAT];
static int x_clock[MAX_STAT];

static int nstat=X_LAST;
static int stat_n[MAX_STAT],stat_idle;
static double xstat_start[MAX_STAT],stat_time[MAX_STAT],stat_etime[MAX_STAT];
void xstats_clear(void)
{
        int i;
        for (i=0; i<nstat; i++) {
                stat_etime[i] = 0;
                stat_time[i] = 0;
                stat_n[i] = 0;
        }
}

void stat_start(int x)
{
        static int cnt;
	if (x>=MAX_STAT) return;
	if (x<0) return;
        if (!istiming) return;
        x_time[x] = lib_msec();
        x_clock[x] = stats_clock();
}
void stat_end(int x)
{
        if (!istiming) return;
	if (x>=MAX_STAT) return;
	if (x<0) return;
        stat_etime[x]  += ((lib_msec())-x_time[x]);
        stat_time[x]  += (stats_clock()-x_clock[x]);
        stat_n[x]++;
}

static int idle_start,idle_min,idle_count;
void stats_idle1(int st, int t)
{
        if (idle_min==0) idle_min = lib_gmtime();
        stat_idle = st;
        idle_start = t;
}
void stats_idle2(void)
{
        int t = lib_gmtime();
        int delay;
        idle_count += t - idle_start;
        if ((t - idle_min) > 60) {
                delay = t - idle_min;
                stat_usage = (delay - idle_count) * 100.0 / delay; 
                idle_min = t;
                idle_count = 0;
        }
}

char *strclean(char *s)
{
        static char bf[BFSZ];
        ncpy(bf,s,100);
        for (s=bf;*s!=0;s++) {
                if (!isprint(*s)) *s = '?';
        }
        return bf;
}
int nntp_getidle(int *x);

int stat_cmp(const void *aa, const void *bb)
{
        int *a = (int *) aa;
        int *b = (int *) bb;
	if (stat_etime[*a]>stat_etime[*b]) return 1;
	if (stat_etime[*a]<stat_etime[*b]) return -1;
	return 0;
}
static int main_mused,main_last;
String *stats_show(void)
{
        char bf[BFSZ];
	int melap;
        static String *stat;
        int idle,since;
        int i,ii;
	static int p[100];
        int cps=CLOCKS_PER_SEC;

	if (strcmp(x_names[X_LAST],"LASTONE_INSERT_ABOVE")!=0) emsg("DIDN'T MATCH X_NAMES\n");

        if (stat==NULL) stat = str_new();

   str_copysn(stat,"       CMD      CPU        ELAP    CALLS CPUAVG   ELAPAVG\015\012");


	for (i=0; i<nstat; i++) p[i] = i;
	if (nstat>2) qsort(p,nstat,sizeof(int),stat_cmp);


        for (ii=0; ii<nstat; ii++) {
		i = p[ii];
                if (cps==0) break;
		if (stat_time[i]==0) continue;
                if (stat_n[i]>0) {
          lprintf(bf,BFSZ-1,"%10s  %9.2f %9.2f %7d %6.4f  %6.4f\015\012"
                                ,strclean(x_names[i])
                                ,stat_time[i]/cps,stat_etime[i]/1000.0,stat_n[i]
                                ,(stat_time[i]/cps)/stat_n[i]
                                ,(stat_etime[i]/1000.0)/stat_n[i]);
                  str_addsn(stat,bf);
                }
        }
        str_addsn(stat,item_xover_status());
        
        idle = nntp_getidle(&since);
        if (since>0) {
                lprintf(bf,BFSZ-1,"       Busy %.2g%% for the last %d seconds\015\012",
                        ((double) (since-idle) *100.0)/since,since/1000);
                str_addsn(stat,bf);
        }


	
	if (main_last!=0) {
		melap = lib_msec() - main_last;
		lprintf(bf,BFSZ-1,"       Main Mutex %.2g%% \r\n",(double) 100.0*main_mused/(double)melap);
		str_addsn(stat,bf);
	}
	main_mused = 0;
	main_last = lib_msec();

        if (!istiming) {
                lprintf(bf,BFSZ-1,"+++ Timing currently disabled, 'timing false' in dnews.conf\015\012");
                str_addsn(stat,bf);
        }
        return stat;
}
int stat_mutex(int xmsec)
{
	if (main_last==0) main_last = lib_msec();
	main_mused += xmsec;
	return TRUE;
}

/*----------------------------------------------------------------------*/
static char *sf_name[120];
static int nsf;
static int sf_delay[120],sf_nreject[120],sf_npost[120],sf_ndup[120];
static int sf_last_tot[120];
static double sf_size[120];
static double sf_rsize[120];
static double sf_ratio[120];
#define NSF 80
void feed_out_summary(void);
void stats_show_changes(void)
{
        int i;
        int tot;
        for (i=0; i<nsf; i++) {
          tot = sf_npost[i] + sf_nreject[i];
          if (tot != sf_last_tot[i]) {
                sf_last_tot[i] = tot;
                if (sf_delay[i] == 0) sf_delay[i] = 1;
                imsg("Feed in summary %s Posts=%d Rejects=%d Dup=%d  %.2f/s  %.2fk/s %dk\n"
                                ,sf_name[i],sf_npost[i],sf_nreject[i],sf_ndup[i]
                                ,(sf_nreject[i] + sf_npost[i]) / (double) sf_delay[i]
                                ,(sf_size[i]/1000.0) / (double) sf_delay[i],(int) (sf_size[i]/1000));
          }
        }
        feed_out_summary();
}
void stats_feed(char *name, int delay, int nreject, int npost, int ndup, double size, double ratio, double total)
{
        int i;
        char bf[BFSZ];
	if (npost<0) return;
	if (npost>100000000) return;
        lprintf(bf,BFSZ-1,"%s bytes/s",name);
        gstat_xadd(bf,(int) size);
        for (i=0; i<nsf; i++) {
                if (strcmp(name,sf_name[i])==0) goto foundit;
        }
        if (nsf>=NSF) return;
        if (nsf<NSF) {
                i = nsf++;      
                sf_name[i] = mystrdup(name,SD_NCMD);
        }
foundit:
        sf_nreject[i] += nreject;
        sf_npost[i] += npost;
        sf_ndup[i] += ndup;
        sf_delay[i] += delay;
        sf_size[i] += size;
        sf_rsize[i] += total;
        if (ratio!=0) sf_ratio[i] = ratio;
}
void stats_clear(void)
{
        int i;
        for (i=0; i<nsf; i++) {
                sf_delay[i] = 0;
                sf_nreject[i] = 0;
                sf_size[i] = 0;
                sf_rsize[i] = 0;
                sf_npost[i] = 0;
                sf_ndup[i] = 0;
        }
}
String *stats_show_in(void)
{
        char bf[BFSZ];
        static String *stat;
	char sratio[BFSZ];
        int i;
	int nfound=0;
	int t_npost,t_nreject,t_ndup,t_delay;
	double t_size;
        if (stat==NULL) stat = str_new();

	t_npost=t_nreject=t_ndup=t_delay=0;
	t_size=0;

	str_copysn(stat,"Sites that have sent us articles since the last expire:\015\012");
   str_addsn(stat,"  Posts Rejects     Dup   Total Articles/S  Kbytes/s         K        Site\015\012");

        for (i=0; i<nsf; i++) {
	  strcpy(sratio,"");
	  if (sf_ratio[i]!=0) lprintf(sratio,BFSZ-1,"(%.2f%%) ",sf_ratio[i]);
          if (sf_delay[i] == 0) sf_delay[i] = 1;
	  if (sf_size[i]==0) sf_size[i] = 1;
          lprintf(bf,BFSZ-1,"%7d %7d %7d %7d    %7.2f   %7.2f  %7dk %.2f%% %s%s\015\012"
                                ,sf_npost[i],sf_nreject[i],sf_ndup[i]
                                ,sf_nreject[i] + sf_npost[i] + sf_ndup[i]
                                ,(sf_nreject[i] + sf_npost[i]) / (double) sf_delay[i]
                                ,(sf_size[i]/1000.0) / (double) sf_delay[i],(int) (sf_size[i]/1000),((sf_rsize[i])/sf_size[i])*100.0,sratio,sf_name[i]);
                  str_addsn(stat,bf);
		t_npost += sf_npost[i];
		t_nreject += sf_nreject[i];
		t_ndup += sf_ndup[i];
		if (sf_delay[i]>t_delay) t_delay = sf_delay[i];
		t_size += sf_size[i];
		nfound++;
        }

	if (nfound>1) {
		  strcpy(sratio,"");
	        lprintf(bf,BFSZ-1,"%7d %7d %7d %7d    %7.2f   %7.2f  %7dk %.2f%% %s%s\015\012"
                                ,t_npost,t_nreject,t_ndup
                                ,t_nreject + t_npost + t_ndup
                                ,(t_nreject + t_npost) / (double) t_delay
                                ,(t_size/1000.0) / (double) t_delay,(int) (t_size/1000),(double) 0.0,sratio,"Total");
                  str_addsn(stat,bf);
	}

        return stat;
}
char *get_dconfig(void);
void cmd_showfile(int chan, char *fname)
{
        FILE *f;
        int i;
        char xdir[BFSZ];
        char bf[BFSZ];
        char bf2[BFSZ];
        char *s;
        char fullname[BFSZ];

       	strlwr(fname);
	if (strstr(fname,"users.dat")!=NULL) goto failit;
	if (strstr(fname,"tellnews.")!=NULL) goto failit;
	if (strstr(fname,"passwd")!=NULL) goto failit;

	/* Now check the name does contain certain things */
	if (strstr(fname,".log")!=NULL) goto isok;
	if (strstr(fname,".conf")!=NULL) goto isok;
	if (strstr(fname,".dat")!=NULL) goto isok;
	goto failit;
isok:	;

	if ( (strchr(fname,'/')!=NULL)  ||  (strchr(fname,'\\')!=NULL) || (strstr(fname,"..")!=NULL) )  {
failit:	        chan_write_s(chan,"Access denied sorry\015\012");		
	        chan_write_s(chan,".\015\012");		
		return;
	}
	
	
	ncpy(xdir,fname,BFSZ-1);
        s = strchr(xdir,':');
        if (s!=NULL) {
                *s = 0;
                ncpy(fname,s+1,100);
        } else {
                ncpy(xdir,"config",BFSZ-1);
        }

	
	strcpy(fullname,"none");
        if (strcmp(xdir,"config")==0) ncpy(fullname,file_config(fname),BFSZ-1);
        if (strcmp(xdir,"workarea")==0) ncpy(fullname,file_work(fname),BFSZ-1);
        if (strcmp(xdir,"log")==0) ncpy(fullname,file_log(fname),BFSZ-1);
        if (strcmp(fname,"dnews.conf")==0) ncpy(fullname,get_dconfig(),BFSZ-1);

        f = fopen(fullname,"r");
        if (f==NULL) { goto failed;}
        for (i=0;(!feof(f)) && (i<2000);i++) {
                if (fgets(bf,BFSZ-10,f)==NULL) break;
                s = strchr(bf,'\n');
                if (s!=NULL) *s = 0;
                if (strcmp(bf,".")==0) strcpy(bf,"..");
                lprintf(bf2,BFSZ-1,"%s\015\012",bf);
                chan_write_s(chan,bf2);
        }                       
        chan_write_s(chan,".\015\012");
        fclosep(f);
        return;
failed:
        lprintf(bf2,BFSZ-1,"Could not read %s %s\015\012",fullname,strerror(errno));
        chan_write_s(chan,bf2);
        return;
}

void cmd_emsg_tail(int chan, char *lines)
{
        FILE *f;
        char *s;
        char bf[BFSZ];
        int i;
        char bf2[BFSZ];
        char fullname[BFSZ];
        int offset;
        int skip=FALSE;
        offset = -atoi(lines)*60;
        if (offset==0) offset = -2000;
        
        emsg_flush();
        ncpy(fullname,file_log("dnews.log"),BFSZ-1);
        f = fopen(fullname,"r");
        if (f==NULL) {filemsg(fullname); goto failed;}
        fseek(f,0,SEEK_END);
        if (ftell(f)+offset >0) skip = TRUE;
        else offset = -ftell(f);
        fseek(f,offset,SEEK_END);
        if (skip) fgets(bf,BFSZ-10,f);
        for (i=0;(!feof(f)) && (i<atoi(lines)+100) && (i<2000);i++) {
                if (fgets(bf,BFSZ-10,f)==NULL) break;
                s = strchr(bf,'\n');
                if (s!=NULL) *s = 0;
                if (strcmp(bf,".")==0) strcpy(bf,"..");
                lprintf(bf2,BFSZ-1,"%s\015\012",bf);
                chan_write_s(chan,bf2);
        }                       
        fclosep(f);
        chan_write_s(chan,".\015\012");
        return;
failed:
        lprintf(bf2,BFSZ-1,"Could not read %s\015\012",fullname);
        chan_write_s(chan,bf2);
        return;
}
void control_check(int chan, char *fname)
{
        FILE *f;
        String *xitem;
        char bf[BFSZ];
        char *s;

        xitem = str_new();
        f = fopen(file_config(fname),"r");
        if (f==NULL) {
                filemsg(fname);
                lprintf(bf,BFSZ-1,"Cannot read {%s}, it must be in the config directory\015\012",file_config(fname)); chan_write_s(chan,bf);
                return;
        } 

        for (;!feof(f);) {
                fgets(bf,BFSZ-6,f);
                s = strchr(bf,'\015'); if (s!=NULL) *s = 0;
                s = strchr(bf,'\012'); if (s!=NULL) *s = 0;
                strcat(bf,"\015\012");
                str_addsn(xitem,bf);
        }
        check_groups_any();
        check_groups(xitem);
        lprintf(bf,BFSZ-1,"File processed ok, check check.log for details \015\012"); chan_write_s(chan,bf);
        str_free(xitem);
        fclosep(f);
}

static int dlast_time;
void lib_recheck_space(void)
{
        dlast_time = 0;
        lib_checkspace();
}
extern int xtime_now;
static int s_last_return;
void lib_setpaused(void)
{
        s_last_return = FALSE;
}
static char disk_space[400];
char *lib_status(void)
{
        return disk_space;
}
int lib_checkspace(void)
{
        static int email=TRUE;
        int w,h,l,s,ww,hh,ss,uu,ll;
        char *p;
        static int last_uu;
        char *status;
        char subj[BFSZ];
        char *reason;
	static char rbf[BFSZ];
   
        /* Assume this is time consuming, so don't do it too often */
        if (xtime_now-dlast_time<120) return s_last_return;
        dlast_time = lib_gmtime();
        l = lib_freespace(config_get(C_LOGDIR));
        w = lib_freespace(config_get(C_WORKAREA));
        h = lib_freespace(config_get(C_HISTORY));       
        s = lib_freespace(config_get(C_SPOOL)); 

        ll = atoi(config_get(C_LOGDIR_MIN))*1000;
        ww = atoi(config_get(C_WORKAREA_MIN))*1000;
        hh = atoi(config_get(C_HISTORY_MIN))*1000;
        /*imsg("History size %d to %d\n",hh,hist_diskspace()*12/10);*/
        if (hh < (hist_diskspace()*10/10)) {
                hh = (hist_diskspace()*10/10);
                dmsg("db: Adjusting hist_min to %dMB\n",hh/1000);
        }
	/* if was ok, and we are expring now, then history can grow more*/ 
	if (email) if (is_expring()) { 
		hh = hh / 5;
	}

        ss = atoi(config_get(C_SPOOL_MIN))*1000;
        if (ll==0) ll=1000;

        s_last_return = TRUE;
        reason = "";
        if (ll>l) {s_last_return = FALSE; reason = "** Log area full\r\n";}
        if (ww>w) {s_last_return = FALSE; reason = "** Workarea area full\r\n";}
	
	if (get_expire_mode() || is_expiring()) {
         if ((hh/2)>h) {
		s_last_return = FALSE; 
		lprintf(rbf,BFSZ-1,"** History area full (less than history.dat/2 size free) %d > %d\r\n",hh/2,h);
		reason = rbf;
	 }
	} else {
         if (hh>h) {
		s_last_return = FALSE; 
		lprintf(rbf,BFSZ-1,"** History area full (less than history.dat size free) %d > %d\r\n",hh,h);
		reason = rbf;
	 }
	}
	if (!hist_ispage()) {
#ifndef WIN32
#ifndef HAS_SEEK64


	if (hist_diskspace()>2100000) { 
		s_last_return = FALSE; 
		lprintf(rbf,BFSZ-1,"** History over 2gig limit reduce pile size of non binary piles %dMB > %dMB\r\n",hist_diskspace()/1024,2100000/1024);
		reason = rbf;
	}
#endif
#else
#ifdef HIST64
	/* No history file limit*/
#else
	if (hist_diskspace()>4000000) { 
		s_last_return = FALSE; 
		lprintf(rbf,BFSZ-1,"** History over 4gig limit reduce pile size of non binary piles %dMB > %dMB\r\n",hist_diskspace()/1024,2100000/1024);
		reason = rbf;
	}
#endif
#endif
#ifdef HAS_SEEK64
	if (hist_diskspace()>4000000) { 
		s_last_return = FALSE; 
		lprintf(rbf,BFSZ-1,"** History over 4gig limit reduce pile size of non binary piles %dMB > %dMB\r\n",hist_diskspace()/1024,2100000/1024);
		reason = rbf;
	}
#endif
	}

        if (ss>s) {s_last_return = FALSE; reason = "** Spool area full\r\n";}
        if (is_limited()) {
         dmsg("db: Disk limit check %dMB %d , increase to 2000 \n",diskuse_get()/100,20);
         if ((diskuse_get()/100)>2000) {
                s_last_return = FALSE; reason = "** 2 Gig License limit exceeded\r\n";
         }
        }

        if (lib_pused()>104) {s_last_return = FALSE;  reason = "** Spool area over 104%\r\n";}

        lprintf(disk_space,BFSZ-1,"Used %dMB=%d%%, Free work %dMb, hist %dMb, spool %dMb, log %dMb\015\012%s",diskuse_get()/100,lib_pused(),w/1000,h/1000,s/1000,l/1000,reason);
/*imsg("disk_space %s",disk_space);*/
        uu = diskuse_get()/100;
        /* if (uu!=last_uu) imsg("lib: %s \n",disk_space); */
        last_uu = uu;
        if (email!=s_last_return) {
                if (!s_last_return) emsg("lib: Not enough space, stopping feeds %s",disk_space);
                else emsg("lib: Restarting feeds %s",disk_space);
                email = s_last_return;
                if (s_last_return) status = "Starting";
                else status = "Pausing";
                lprintf(subj,BFSZ-1,"News %s %s",status,disk_space);
                p = strchr(subj,'\015'); if (p!=NULL) *p = 0;
                dnews_manager_report(subj);
        }
        
        return s_last_return;
}

#define TELL_FILE file_work("tell.txt")
void ncmd_tell(int x)
{
        static int doneit;
        FILE *f;
        if (doneit) return;
        suck_update_clear();
        feed_addmail();
        doneit = TRUE;
        f = fopen(TELL_FILE,"w");
        if (f==NULL) return;
        fprintf(f,"Subject: Message for CHRISP\n");
        fprintf(f,"From: netwin@netwinsite.com\n");
        fprintf(f,"\n");
        fprintf(f,"Give this file to CHRISP urgently %d\n",x);
        fprintf(f,"%s\n",config_get(C_MYNAME));
        fprintf(f,"%s\n",config_get(C_MANAGER));
        fprintf(f,"%s\n",config_get(C_NNTP_SUCK1));
        fprintf(f,"%s\n",host_details());
        fprintf(f,"%s\n",nntp_hostname());
        fclosep(f);
        lib_mail_file("netwin@netwinsite.com","SERIOUS, CODE 111",TELL_FILE);
        remove(TELL_FILE);
}
static Sary *xstack;
static int nstack=0;
char *xspl_next(int chan);
int noremove(char *s)
{
        imsg("remove {%s}\n",s);
        return 0;
}

void remove_tree(int chan)
{
        char bf[BFSZ];
        char *i_file;
        char thisdir[BFSZ],lastdir[BFSZ];
        if (xstack==NULL) xstack = s_alloc();
	strcpy(lastdir,"");
        s_set(xstack,nstack++,config_get(C_SPOOL));
        i_file = xspl_next(chan);
        for (;(i_file!=NULL) ; ) {
                if (strncmp(i_file,"db",2)==0) goto skip1;
                if (strstr(i_file,".idx")==NULL) if (strstr(i_file,".itm")==NULL) {
skip1:                  ;
                        /*
                        lprintf(bf,BFSZ-1,"Skipped %s\r\n",i_file);
                        chan_write_s(chan,bf);
                        */
                        goto skipit;
                }
                lprintf(bf,BFSZ-1,"Deleting %s\r\n",i_file);
                chan_write_s(chan,bf);
                remove(i_file);
skipit:         lib_dir_split(i_file,thisdir,bf);
                if (strlen(lastdir)>0) if (strcmp(thisdir,lastdir)!=0) {
                        if (strchr(lastdir,'.')==NULL) {
                         lprintf(bf,BFSZ-1,"Deleting Directory %s\r\n",lastdir);
                         chan_write_s(chan,bf);
                          lib_rmdir(lastdir);
                        }
                }
                strcpy(lastdir,thisdir);
                i_file = xspl_next(chan);
        }                       

}
char *xspl_next(int chan)
{
        int i;
        Sary *xdir;
        static char bf[BFSZ],bf2[BFSZ];
        char f_dir[BFSZ];
        char f_name[BFSZ];
        
        if (xstack==NULL) return NULL;
next_one:
        if (nstack==0) return NULL; 
        ncpy(bf,s_line(xstack,--nstack),BFSZ-1);
        lib_dir_split(bf,f_dir,f_name);
        if (strcmp(f_name,".")==0) goto next_one;
        if (strcmp(f_name,"..")==0) goto next_one;
        if (strlen(f_name)==0) goto next_one;
        if (strncmp(f_name,"db",2)==0) goto next_one;
        if (strncmp(f_name,"DB",2)==0) goto next_one;
        if (strncmp(f_name,"local",5)==0) goto next_one;
        if (strncmp(f_name,"hist",4)==0) goto next_one;
        if (strncmp(f_name,"HIST",4)==0) goto next_one;
        if (strchr(f_name,'.')==NULL) {
                lib_rmdir(bf);
                goto add_dir;
        }
        return bf;
add_dir:
        /* imsg("remove: Scanning %s\n",bf);*/
        xdir = s_alloc();
        s_wild(xdir,bf,"*");
        for (i=0; i<s_len(xdir); i++) {
                strcpy(bf2,bf);
                strcat(bf2,"/");
                strcat(bf2,s_line(xdir,i));
                s_set(xstack,nstack++,bf2);
        }
        s_free(xdir);
        goto next_one;
}
/*
        Post messages (send takethis)
        Send history requests (send xhlookup)
        Read active.dat changes (xactive group from to n cached)
        Get history results (xhresult chan msgid group num)
        take posts  (takethis)
        Get history requests (xhlookup chan <msgid>)
        Send active.dat changes (poke into output queue for that channel)
*/
String *copy_replace(String *item, char *group, int newmid);

void copy_group(int chan,char *gfrom,char *gto, int newmid)
{
        int xfrom,xto;
        char bf[BFSZ];
        String *newhead,*xitem,*xbody;
        int i;
        int n;
        xfrom = group_find(gfrom);
        xto = group_find(gto);
        if (xfrom<0) {
                lprintf(bf,BFSZ-1,"Cannot find group (%s)\r\n",gfrom);
                chan_write_s(chan,bf);
                return;
        }
        if (xto<0) {
                lprintf(bf,BFSZ-1,"Cannot find group (%s), create it with tellnews newgroup command\r\n",gto);
                chan_write_s(chan,bf);
                return;
        }
                
        n = g_list_start(xfrom);
        for (i=0; (n>=0) ; i++)  {
                xitem = item_head(xfrom,n);
                xbody = item_body(xfrom,n);
                if (xitem==NULL) goto nextx;
                if (xbody==NULL) goto nextx;
                str_addsn(xitem,"\r\n");
                if (xbody!=NULL) str_add(xitem,xbody);
                if ((xitem!=NULL) && (xbody!=NULL)) {
                        /* Copy header, remove 'newsgroups' */
                        newhead = copy_replace(xitem,gto,newmid);
                        if (newhead==NULL) {
                                lprintf(bf,BFSZ-1,"copy_replace failed\015\012");
                                chan_write_s(chan,bf);
                                return;
                        }
                        str_addsn(newhead,"\r\n");
                        if (!head_replace(xitem,newhead)) {
                                lprintf(bf,BFSZ-1,"head_replace failed\015\012");
                                chan_write_s(chan,bf);
                                return;
                        }
                        /* set ignore tooold and nofeed */
                        item_setimport(TRUE);
                        item_stopfeeds(TRUE);
			feed_setnorules(TRUE);
                        if (item_suck(xitem)) {
                                lprintf(bf,BFSZ-1,"Copied item %s:%d %s\r\n",gfrom,n,get_ahead(xitem,"Message-ID:")); 
                        } else {
							lprintf(bf,BFSZ-1,"FAILED TO copy %s check dnews.log %s\r\n",get_ahead(xitem,"Message-ID:"),newmid ? "NEWMID" : "OLDMID");     
                        }                       
			feed_setnorules(FALSE);
                        item_setimport(FALSE);
                        item_stopfeeds(FALSE);
                        chan_write_s(chan,bf);
                }
nextx:          n = g_list_next();
        }
}
void copy_items(int chan, char *gfrom, char *gto, char *subject, char *mid, int dodel)
{
        int xfrom,xto;
        char bf[BFSZ];
        String *xitem;
        int i;
        int n;
        char *mode;
        int start;
        xfrom = group_find(gfrom);
	xto = 0;
        mode = "Copy";
        if (dodel) mode = "Move";
        if (strlen(gto)==0) mode = "Delete";
        lprintf(bf,BFSZ-1,"%s from (%s) to (%s) subject=\"%s\" mid=\"%s\" \n",
                        mode,gfrom,gto,subject,mid);
        chan_write_s(chan,bf);
        imsg("ncmd: %s",bf);

        if (strlen(gto)>0) xto = group_find(gto);
        if (xfrom<0) {
                lprintf(bf,BFSZ-1,"Cannot find group (%s)\r\n",gfrom);
                chan_write_s(chan,bf);
                return;
        }
        if ((strlen(gto)>0) && (xto<0)) {
                lprintf(bf,BFSZ-1,"Cannot find group (%s), create it with tellnews newgroup command\r\n",gto);
                chan_write_s(chan,bf);
                return;
        }
                
        n = g_list_start(xfrom);
        for (i=0; (n>=0) ; i++)  {
                xitem = item_head(xfrom,n);
                if (xitem==NULL) goto nextx;
                if (strlen(subject)>0) {
                        ncpy(bf,get_ahead(xitem,"Subject:"),BFSZ-10);
                        strlwr(bf);
                        strlwr(subject);
                        imsg("ncmd: %d Find (%s) inside (%s)\n",n,subject,bf);
			if (subject[0]=='=') {
				if (strcmp(bf,subject+1)==0) goto docopy;
			} else {
				if (strstr(bf,subject)!=NULL) goto docopy;
			}
                }
                if (strlen(mid)>0) {
                        ncpy(bf,get_ahead(xitem,"Message-ID:"),BFSZ-10);
                        strlwr(bf);
                        strlwr(mid);
                        imsg("ncmd: %d Find (%s) inside (%s)\n",n,mid,bf);
                        if (strstr(bf,mid)!=NULL) goto docopy;
                        ncpy(bf,get_ahead(xitem,"References:"),BFSZ-10); strlwr(bf);
                        imsg("ncmd: %d Find (%s) inside (%s)\n",n,mid,bf);
                        if (strstr(bf,mid)!=NULL) goto docopy;
                }
                goto nextx;
docopy:
		lprintf(bf,BFSZ-1,"Processing item %d, %s\r\n",n,get_ahead(xitem,"Subject:"));
		chan_write_s(chan,bf);

                if (strlen(gto)>0) copy_item(chan,gfrom,gto,n);
                if (dodel) {
                        item_delete(xfrom,n);
                        db_clear_cache("copy items");
                }
nextx:  
                
                start = g_list_start(xfrom);
                for (;start<=n;) {
                        start = g_list_next();
                        if (start<0) break;
                }
                n = start;
        }
}
void copy_item(int chan,char *gfrom,char *gto, int n)
{
        int xfrom,xto;
        char bf[BFSZ];
        char subj[BFSZ];
        String *newhead,*xitem,*xbody;
        xfrom = group_find(gfrom);
        xto = group_find(gto);
        if (xfrom<0) {
                lprintf(bf,BFSZ-1,"Cannot find group (%s)\r\n",gfrom);
                chan_write_s(chan,bf);
                return;
        }
        if (xto<0) {
                lprintf(bf,BFSZ-1,"Cannot find group (%s), create it with tellnews newgroup command\r\n",gto);
                chan_write_s(chan,bf);
                return;
        }
                
        xitem = item_head(xfrom,n);
        xbody = item_body(xfrom,n);
        if (xitem!=NULL) str_addsn(xitem,"\r\n");
        if (xbody!=NULL) str_add(xitem,xbody);
        if ((xitem!=NULL) && (xbody!=NULL)) {
                /* Copy header, remove 'newsgroups' */
                newhead = copy_replace(xitem,gto,FALSE);
                if (newhead==NULL) {
                        lprintf(bf,BFSZ-1,"copy_replace failed\015\012");
                        chan_write_s(chan,bf);
                        return;
                }
                str_addsn(newhead,"\r\n");
                if (!head_replace(xitem,newhead)) {
                        lprintf(bf,BFSZ-1,"head_replace failed\015\012");
                        chan_write_s(chan,bf);
                        return;
                }
                /* set ignore tooold and nofeed */
                item_setimport(TRUE);
                item_stopfeeds(TRUE);
                if (item_suck(xitem)) {
                        ncpy(subj,get_ahead(xitem,"Subject:"),50);
                        lprintf(bf,BFSZ-1,"Copied item %s:%d %s %s\r\n",gfrom,n,get_ahead(xitem,"Message-ID:"),subj); 
                } else {
                        lprintf(bf,BFSZ-1,"FAILED TO copy %s check dnews.log\r\n",get_ahead(xitem,"Message-ID:"));     
                }                       
                item_setimport(FALSE);
                item_stopfeeds(FALSE);
                chan_write_s(chan,bf);
        }
        
}

String *copy_replace(String *item, char *newsgroup, int newmid)
{
        static String *newhead;
        char *e;
        char *z,*z2;
        int len;
        char mid[BFSZ];
        char bf[BFSZ];
        char ref[BFSZ];
        char ref2[BFSZ];
        char *s = str_s(item);
        int remove=FALSE;
        int h;
		int newid;

		newid = (time(NULL)/100) % 4000;

        strcpy(ref,"");
        h = group_getgroupid(group_find(newsgroup));
        str_lock(item);
        if (newhead==NULL) newhead= str_new();
        strcpy(mid,"nomidfound>");
        str_clearbig(newhead);
        lprintf(bf,BFSZ-1,"Newsgroups: %s\r\n",newsgroup);
        str_addsn(newhead,bf);
        for (;s!=NULL && *s!=0;) {
                if (strncmp(s,EOL,strlen(EOL))==0) break;
                if ((*s!=' ') && (*s!='\t')) remove = FALSE;
                if (strncmpnc(s,"Newsgroups:",strlen("Newsgroups:"))==0) {
                        remove = TRUE;
                }
                e = strstr(s+1,EOL); if (e==NULL) break;
                if (strncmpnc(s,"References:",strlen("References:"))==0) {
                        remove = TRUE;
                        len = e-(s+12);
                        if (len>400) len=400;
                        ncpy(ref,s+12,len);
                } 
                
                if (strncmpnc(s,"Message-ID:",strlen("Message-ID:"))==0) {
                        remove = TRUE;
                        z = strchr(s,'<');
                        z2 = strchr(s,'>');
                        if (z!=NULL) if (z2!=NULL) {
                                ncpy(mid,z+1,z2-z);
                        }
                }
                if (remove) {
                        /* imsg("Removing line from header %.20s\n",s);*/
                } else str_adds(newhead,s,e-s+2);
                s = strstr(s+1,EOL);
                if (s!=NULL) s+=strlen(EOL);            
        }
        lprintf(bf,BFSZ-1,"Message-ID: <%d_%s\r\n",h,mid); 
		if (newmid) {
			lprintf(bf,BFSZ-1,"Message-ID: <%d%d_%s\r\n",newid,h,mid); 
			imsg("copy: new msgid used %s",bf);
		}
		str_addsn(newhead,bf);
        if (strlen(ref)>0) {
                strcpy(ref2,"References:");
                s = strtok(ref," \t\r\n");
                for (;s!=NULL;) {
                        if (*s!='<') goto cont;
                        if (s[strlen(s)-1]!='>') goto cont;
                        lprintf(bf,BFSZ-1," <%d_%s",h,s+1);
						if (newmid) {
			                  lprintf(bf,BFSZ-1," <%d%d_%s",newid,h,s+1);
						}
                        strcat(ref2,bf);
                        if (strlen(ref2)>400) break;
cont:                   s = strtok(NULL," \t\r\n");
                }       
                lprintf(bf,BFSZ-1,"%s\r\n",ref2); str_addsn(newhead,bf);
        }
        str_unlock(item);

        return newhead;
}
void cmd_test_malloc(int chan, int mb)
{
        char bf[BFSZ];
        if (mb>500) mb = 500;
        if (mb==0) mb = 60;
        emsg("Testing memory %d mbytes \n",mb);
        lprintf(bf,BFSZ-1,"Allocated %d of %d MB's attempted\r\n",lib_test_malloc(mb),mb);
        chan_write_s(chan,bf);
        lprintf(bf,BFSZ-1,"IT IS ESSENTIAL THAT YOU RESTART DNEWS NOW\r\n");
        chan_write_s(chan,bf);
}


int pop_read(int chan, char *xin, int sz);
int pop_fail(char *msg);
int pop_pop(void);
int pop_addcache(char *user, char *pass);
int pop_incache(char *user, char *pass);
int pop_findcache(char *user);
int pop_doauth(int chan, char *user, char *pass);


enum {P_WELCOME,P_USER,P_PASS,P_IGNORE};
static int pstate;
static char *pque_user[1000];
static char *pque_pass[1000];
static int pque_chan[1000];
static int npque;
static int maxucache;
static char **c_user;
static char **c_pass;
static int nc;
static int cupto;
static int pop_open;
static int pop_time;
static int pchan;
int pop_init(void)
{
        static int done_init;
        if (done_init) return TRUE;
        done_init = TRUE;
        maxucache = atoi(config_get(C_AUTH_POPCACHE));
        if (maxucache==0) maxucache = 1000;

        c_user = mymalloc(sizeof(char *) * maxucache);

        memset(c_user,0,sizeof(char *) * maxucache);
        c_pass = mymalloc(sizeof(char *) * maxucache);
        memset(c_pass,0,sizeof(char *) * maxucache);
        
        return TRUE;
}
int pop_quelookup(int chan, char *user, char *pass)
{
        if (pop_incache(user,pass)) return pop_doauth(chan,user,pass);
        if (npque>500) {
                chan_write_s(chan,"502 Authentication error, pop server not responding\015\012");
                emsg("POP lookup queue is full, pop server must be failing\n");
                return FALSE;
        }
        nntp_setpending(chan,TRUE);
        pque_user[npque] = mystrdup(user,SD_NCMD);
        pque_pass[npque] = mystrdup(pass,SD_NCMD);
        pque_chan[npque] = chan;
        npque++;
        pop_backgnd();
        return TRUE;
}
char *pop_strip_domain(char *s)
{
	static char bf[BFSZ];
	ncpy(bf,s,200);
	s = strchr(bf,'@');
	if (s!=NULL) *s = 0 ;
	return bf;
}
/*	auth_pophost netwin.com=pop.netwin.com,ajax.com=mail.ajax.com 
	auth_popdomains ajax.com,fred.com  (then leave @domain on username) 
*/
int list_from_file(char *fname, char *result, int len)
{
	FILE *f;
	char bf[BFSZ];
	char *s;
	if (fname[0]!='/') if (fname[0]!='\\') {
		ncpy(result,fname,len-1);
		return TRUE;
	}
	f = fopen(fname,"r");
	if (f==NULL) {emsg("Could not read (%s)\n",fname); return FALSE;}
	strcpy(result,"");
	for (;!feof(f);) {
		if (fgets(bf,BFSZ-1,f)==NULL) break;
		s = strtok(bf," \r\n\t");
		strcat(result,bf);
		strcat(result,",");
		if ((int) (strlen(result)+100)>len) break;
	}
	fclose(f);
	return TRUE;
}
char *pop_host(void)
{
	char *s;
	char *s2;
	static char host[20000],uhost[BFSZ],user[BFSZ];
	static char rhost[BFSZ];
	ncpy(host,config_get(C_AUTH_POPHOST),BFSZ-1);
	if (strchr(host,'/')!=NULL) list_from_file(host,host,10000);
	if (strchr(host,'\\')!=NULL) list_from_file(host,host,10000);
	if (pque_user[0]==NULL) return host;
	ncpy(user,pque_user[0],200);
	s = strchr(user,'@');
	if (s==NULL) {
		s = strchr(host,','); if (s!=NULL) *s = 0;
		s2 = strchr(host,'=');
		if (s2!=NULL) return s2+1;
		return host;
	} else {
		ncpy(uhost,s+1,200);
		strlwr(host); strlwr(uhost);
		s = strtok(host,",\r\n ");
		for (;s!=NULL;) {
			ncpy(rhost,s,BFSZ-1);
			s2 = strchr(s,'=');
			if (s2!=NULL) {
				ncpy(rhost,s2+1,BFSZ-1);
				*s2 = 0;
			}
			if (strcmp(s,uhost)==0) return rhost;
			s = strtok(NULL,",\r\n");			
		}
		emsg("User attempted to use invalid pop server (%s)\n",pque_user[0]);
		return "";
	}
}
int pop_backgnd(void)
{
        char bf[BFSZ];
	char *s;
        if (pop_open) {
                if ((lib_gmtime()-pop_time)>120) {
                        pop_fail("Timeout waiting for pop response, try later");
                        return TRUE;
                }
                if (!nntp_isopen(pchan)) {
                        pop_fail("Could not open connection to pop server, try later");
                }
                return TRUE;
        }
        if (npque<=0) return TRUE;
	if (pque_user[0] == NULL) pop_pop();
        if (npque<=0) return TRUE;
	s = pop_host();
	if (strlen(s)==0) {
              pop_fail("Invalid domain for pop lookups sorry");
              return TRUE;
	}
        lprintf(bf,BFSZ-1,"%s:110",s); /* config_get(C_AUTH_POPHOST));*/
        imsg("Open pop connection (%s) \n",bf);
        pop_time = lib_gmtime();
        pchan = nntp_open(bf);
        pstate = P_WELCOME;
        pop_open = TRUE;
        chan_setstate(pchan,C_POP_READ);
        return TRUE;
}

int pop_read(int chan, char *xin, int sz)
{
        char bf[BFSZ];
        char in[BFSZ];
        char *s;
	char list[30000];
	char ruser[BFSZ];
        if (sz>=200) sz = 200-1;
        ncpy(in,xin,sz);
        s = strchr(in,'\r'); if (s!=NULL) *s = 0;
        s = strchr(in,'\n'); if (s!=NULL) *s = 0;
        dmsg("pop: Response (%s)\n",in);
        if (pque_chan[0] == -1) return TRUE;
        switch (pstate) {
          case P_WELCOME:
                if (in[0]!='+') {
                        lprintf(bf,BFSZ-1,"Bad pop welcome message (%s)",in);
                        pop_fail(bf);
                        return TRUE;
                }
		ncpy(ruser,pop_strip_domain(pque_user[0]),BFSZ-1);
		if (strlen(config_get(C_AUTH_POP_DOMAINS))>0) {
			s = strchr(pque_user[0],'@');
			if (s!=NULL) {
				list_from_file(config_get(C_AUTH_POP_DOMAINS),list,20000);
				if (group_match(list,s+1)) {
					ncpy(ruser,pque_user[0],BFSZ-1);
					imsg("Domain matched auth_pop_domains so sending full (%.100s) to pop server\n",ruser);
				}
			}
		}
                lprintf(bf,BFSZ-1,"USER %s\r\n",ruser);
                chan_write_s(pchan,bf);
                pstate = P_USER;
                break;
          case P_USER:
                if (in[0]!='+') {
                        lprintf(bf,BFSZ-1,"Bad pop welcome message (%s)",in);
                        pop_fail(bf);
                        return TRUE;
                }
                lprintf(bf,BFSZ-1,"PASS %s\r\n",pque_pass[0]);
                chan_write_s(pchan,bf);
                pstate = P_PASS;
                break;
          case P_PASS:
		if (strstr(in,"get lock")!=NULL) goto isok;
		if (strstr(in,"already connected")!=NULL) goto isok;
                if (in[0]!='+') {pop_fail(in); pstate = P_IGNORE; return TRUE;}
isok:		imsg("Pop lookup succeeded %s\n",in);
                lprintf(bf,BFSZ-1,"QUIT\r\n");
                chan_write_s(pchan,bf);
                nntp_close(pchan);
                pchan = -1;
                pop_open = FALSE;
                pop_addcache(pque_user[0],pque_pass[0]);
                pop_doauth(pque_chan[0],pque_user[0],pque_pass[0]);
                nntp_setpending(pque_chan[0],FALSE);
                pop_pop();
                pstate = P_WELCOME;
                break;
	  case P_IGNORE:
		imsg("Pop got extra data %.100s\n",in);
		return TRUE;
        }
        return TRUE;
}
int pop_fail(char *msg)
{
        char bf[BFSZ];
	int chan = pque_chan[0]; 
        emsg("pop_fail: %s\n",msg);
        if (pchan!= -1) nntp_close(pchan);
        pchan = -1;
        pop_open = FALSE;

	if (pque_pass[0]==NULL) return TRUE;
	if (pque_user[0]==NULL) return TRUE;
	imsg("Trying access.conf since pop lookup failed for %s\n",pque_user[0]);
        if (chan_check(chan,pque_pass[0])) {
		chan_write_s(chan,"281 Ok\015\012");
        } else {
	        lprintf(bf,BFSZ-1,"502 Authentication error, %s\015\012",chan_reason());
                chan_write_s(chan,bf);
        }
        nntp_setpending(pque_chan[0],FALSE);
        pop_pop();
        return TRUE;
}
int pop_pop(void)
{
        int i;
again:
        if (npque<=0) return TRUE;
        if (pque_user[0] != NULL) mystrfree(pque_user[0],SD_NCMD);
        if (pque_pass[0] != NULL) mystrfree(pque_pass[0],SD_NCMD);
        for (i=0; i<npque-1; i++) {
                pque_user[i] = pque_user[i+1];
                pque_pass[i] = pque_pass[i+1];
                pque_chan[i] = pque_chan[i+1];
        }
        npque--;
        if (pque_chan[i]<0) goto again;
        return TRUE;
}
int pop_free(int chan)
{
        int i;
        for (i=0;i<npque; i++) {
                if (pque_chan[i]==chan) {
                        if (pque_user[i]!=NULL) mystrfree(pque_user[i],SD_NCMD); pque_user[i] = NULL;
                        if (pque_pass[i]!=NULL) mystrfree(pque_pass[i],SD_NCMD); pque_pass[i] = NULL;
                        pque_chan[i] = -1;
			if (pop_open) if (i==0) pop_fail("User disconnected so aborting pop lookup");
                }
        }
        return TRUE;
}
int pop_addcache(char *user, char *pass)
{
        int next;
        next = pop_findcache(user);
        if (next<0) {
                if (nc<maxucache) next=nc++;
                else next=cupto++;
        }
        if (c_user[next]!=NULL) mystrfree(c_user[next],SD_NCMD);
        if (c_pass[next]!=NULL) mystrfree(c_pass[next],SD_NCMD);
        c_user[next] = mystrdup(user,SD_NCMD);
        c_pass[next] = mystrdup(pass,SD_NCMD);
        if (cupto>=maxucache) cupto = 0;
        return TRUE;
}
int pop_cache_freeall(void)
{
	int i;
	for (i=0; i<maxucache; i++) {
	        if (c_user[i]!=NULL) mystrfree(c_user[i],SD_NCMD);
	        if (c_pass[i]!=NULL) mystrfree(c_pass[i],SD_NCMD);
		c_user[i] = NULL;
		c_pass[i] = NULL;
	}
	return TRUE;
}
int pop_cache_freeone(char *user)
{
	int i = pop_findcache(user);
	if (i<0) return FALSE; 
        if (c_user[i]!=NULL) mystrfree(c_user[i],SD_NCMD);
        if (c_pass[i]!=NULL) mystrfree(c_pass[i],SD_NCMD);
	c_user[i] = NULL;
	c_pass[i] = NULL;
	return TRUE;
}
int pop_incache(char *user, char *pass)
{
        int i = pop_findcache(user);
        if (i<0) return FALSE;
        if (strcmp(c_pass[i],pass)==0) {
                imsg("Found user (%s) in cache\n",user);
                return TRUE;
        }
        return FALSE;
}
int pop_findcache(char *user)
{
        int i;
        for (i=0; i<nc; i++) if (c_user[i]!=NULL) if (strcmp(user,c_user[i])==0) return i;
        return -1;
}
void auth_setok(int x);
int pop_doauth(int chan, char *user, char *pass)
{
        auth_setok(TRUE);
        nntp_setpending(chan,FALSE);

        if (chan_check(chan,pass)) {
                auth_setok(FALSE);
                if (config_true(C_USER_SINGLE)) {
                        if (chan_otheruser(chan)) {
                                chan_write_s(chan,"502 Authentication error, you are already connected from another address\015\012");
                                return TRUE;
                        }
                } 
                chan_write_s(chan,"281 Ok\015\012");
        } else {
                chan_write_s(chan,"502 Authentication error\015\012");
        }
        auth_setok(FALSE);
        return TRUE;
}
void nntp_setip(int c, char *ip);

int ncmd_xdauthent(int chan, char *in)
{
        char user[BFSZ],tpass[BFSZ],pass[BFSZ];
        char ip[BFSZ];
	char reason[BFSZ];
	char tmp[BFSZ];
        ncpy(ip,get_word(in,1),BFSZ-1);
        ncpy(user,get_word(in,2),BFSZ-1);
        ncpy(tpass,get_word(in,3),BFSZ-1);
        strcpy(pass,"freddxxxxxxx");

	if (config_true(C_LOG_USERS)) wmsg("users: %s authent %s\n",chan_getipnum(chan),user);
        
/*        if (strcmp(tpass,conf_getpass())!=0) {*/
	if(!conf_pass_try(tpass,chan,FALSE,FALSE,reason)) {
                lprintf(tmp,BFSZ-1,"502 Authentication error, tellnews_pass set incorrectly in dnewsweb.ini get it from tellnews.pass file %s\015\012",reason);
		chan_write_s(chan,tmp);
                return TRUE;
        }
        nntp_setip(chan,ip);
        if (nntp_isclosing(chan)) return TRUE;
        if (strcmp(user,"(none)")!=0) {
                chan_setuser(chan,user);
                auth_setok(TRUE);
                if (chan_check(chan,pass)) {
                        auth_setok(FALSE);
                        if (config_true(C_USER_SINGLE)) {
                                if (chan_otheruser(chan)) {
                                        chan_write_s(chan,"502 Authentication error, you are already connected from another address\015\012");
                                        return TRUE;
                                }
                        } 
			if (config_true(C_LOG_USERS)) wmsg("users: %s authent ok\n",chan_getipnum(chan));
                        chan_write_s(chan,"281 Ok, Authentication accepted\015\012");
                } else {
                        chan_write_s(chan,"502 Authentication error\015\012");
                }
                auth_setok(FALSE);
                return TRUE;
        }
        chan_write_s(chan,"281 Ok, Authentication accepted\015\012");
        return TRUE;
        
        
}

typedef struct {int group; int num; int date; } Hlist;
static Hlist *hlist;
int hlist_cmp(const void *aa, const void *bb);
int hlist_cmp(const void *aa, const void *bb)
{
        Hlist *a = (Hlist *) aa;
        Hlist *b = (Hlist *) bb;
        return a->date - b->date;
}


#define MAXLIST 20000
#define MAX_LINE 30000
void cmd_extract(int chan, char *groups)
{
        String *head,*body;
        int i;
        char agroup[BFSZ];
        int g;
        char *s1,*s2;
        char *s;
        FILE *f;
        int num;
        int adate;
        int len;
        char bf[MAX_LINE];
        char *h;
        int nhlist=0;

        chan_write_s(chan,"500 Warning: this command locks up the server for several minutes\015\012");
        
        h = hist_line(0);  /* Finds first non expired item after 'date' */
        if (h==NULL) goto done;
        
        hlist = mymalloc(sizeof(Hlist) * MAXLIST);
        nhlist = 0;
        for (; h!=NULL; ) {
	        s = hist_groupfirst(h);
                for (;s!=NULL;) {
			if (group_match(groups,s)) break;			
		        s = hist_groupnext();
		}
		if (s==NULL) goto cont;
                
		ncpy(agroup,s,BFSZ-50);
                g = group_find(agroup);
                if (g<0) goto cont;
                adate = hist_date_posted(h);
                num = hist_groupnextnum();
                
                if (nhlist>=MAXLIST) break;
                hlist[nhlist].group = g;
                hlist[nhlist].num = num;
                hlist[nhlist].date = adate;
                nhlist++;               

cont:
                h = hist_next();                        
        }
done:

        if (nhlist<=0) goto finished;
        qsort(hlist,nhlist,sizeof(Hlist),hlist_cmp);
        f = fopen(file_work("backup.bag"),"wb");
        if (f==NULL) {
                filemsg("Unable to open output file\n");
                goto finished;
        }
	imsg("done step one %d\n",nhlist);
        for (i=0; i<nhlist; i++) {
                head = item_head(hlist[i].group,hlist[i].num);
                body = item_body(hlist[i].group,hlist[i].num);
                if (head==NULL) continue;
                if (body==NULL) continue;
                fprintf(f,"#! rnews 0\n");

                s1 = str_s(head);
                for (;s1!=NULL;) {
                        s2 = strchr(s1,'\015');
                        if (s2==NULL) break;
                        len = (s2-s1);
                        if (len>MAX_LINE-3) len = MAX_LINE-3;
                        strncpy(bf,s1,len);
                        bf[len] = '\n';
                        bf[len+1] = 0;
                        if (strcmp(bf,"..\n")==0) { strcpy(bf,".\n");  len = 1; }
                        if (fwrite(bf,1,len+1,f)!=(size_t) len+1) {filemsg("Write failed to uucp file\n"); break;}
                        if (*(s2+1)!='\012') break; 
                        s1 = s2 + 2;
                }
                strcpy(bf,"\n");
                fwrite(bf,1,1,f);
                s1 = str_s(body);
                for (;s1!=NULL;) {
                        s2 = strchr(s1,'\015');
                        if (s2==NULL) break;
                        len = (s2-s1);
                        if (len>MAX_LINE-3) len = MAX_LINE-3;
                        strncpy(bf,s1,len);
                        bf[len] = '\n';
                        bf[len+1] = 0;
                        if (strcmp(bf,"..\n")==0) { strcpy(bf,".\n");  len = 1; }
                        if (fwrite(bf,1,len+1,f)!=(size_t) len+1) {filemsg("Write failed uucp\n"); break;}
                        if (*(s2+1)!='\012') break; 
                        s1 = s2 + 2;
                }

        }
        fclosep(f);

finished:
        
        if (hlist!=NULL) myfree(hlist);
        hlist = NULL;
        lprintf(bf,MAX_LINE-1,"%d articles sent to bag file\015\012",nhlist);
        chan_write_s(chan,bf);
        chan_write_s(chan,".\015\012");
}

#define SPOST_MAX 1000
static char *spost_ipnum[SPOST_MAX];
static int spost_n[SPOST_MAX];
static int spost_last[SPOST_MAX];
int spam_postfind(char *s)
{
        int i;
        for (i=0; i<SPOST_MAX; i++) {
                if (spost_ipnum[i]!=NULL) if (strcmp(s,spost_ipnum[i])==0) return i;
        }
        return -1;
}
int spam_postn(void)
{
        int n = atoi(config_get(C_SPAM_NPOST));
        if (n==0) n = 300;
        return n;
}
int spam_postdelay(char *ipnum)
{
        int delay;
	int nposts = pf_addone(ipnum,1);
        int n = spam_postn();

        delay = nposts - n;

        if (delay>0) delay = (int) delay*delay;
        if (delay<=0) return 0;
        emsg("spam: Detected local spammer, %d posts from (%s) using delay of %d seconds (increase spam_npost to disable this check)\n",
                nposts,ipnum,delay);
        return delay;
}
int spam_postdelay_old(char *ipnum)
{
        int delay;
        int i = spam_postfind(ipnum);
        int n = spam_postn();

	if (i<0) return 0;
        delay = spost_n[i]-n;

        if (delay>0) delay = (int) delay*delay;
        if (delay<=0) return 0;
        emsg("spam: Detected local spammer, %d posts from (%s) using delay of %d seconds (increase spam_npost to disable this check)\n",
                spost_n[i],ipnum,delay);
        return delay;
}
int spam_postadd(char *ipnum)
{
        static int snext;
        int i = spam_postfind(ipnum);
        if (i<0) {
                snext++;
                if (snext>=SPOST_MAX) snext = 0;
                i = snext;
                if (spost_ipnum[i]!=NULL) mystrfree(spost_ipnum[i],SD_NCMD);
                spost_ipnum[i] = mystrdup(ipnum,SD_NCMD);
                spost_n[i] = 0;
        }
        spost_n[i] ++;
        spost_last[i] = lib_gmtime();
        return spost_n[i];
}
void spam_postclear(void)
{
        int i;
        for (i=0; i<SPOST_MAX; i++) {
                spost_n[i] = 0;         
        }
}
void spam_postage(void)
{
	static int last;
	int pten;

	if ((lib_gmtime()-last) < 60*10) {
		return;
	}
	if (is_readonly()) return;
	last = lib_gmtime();
	pten = (spam_postn()/3);
	if (pten<1) pten = 1;
	pf_age(pten);
}
char *spam_postshow(void)
{
        int i;
        int t = lib_gmtime();
        char *out;
        static char bf[2000];
        out = bf;
        for (i=0; i<SPOST_MAX; i++) {
                if (spost_ipnum[i]==NULL) continue;
                lprintf(out,BFSZ-1,"%s %d posts, last post %d seconds ago\r\n",spost_ipnum[i],spost_n[i],t-spost_last[i]);
                out += strlen(out);
                if (strlen(bf)>1500) break;
        }
        return bf;
}
void ncmd_backgnd(void) /* Called once per second */
{
        static int last,wlast;
        int idle,since;
	int now=lib_gmtime();
        if (last==0) last = lib_gmtime();

        if (!config_true(C_GSTAT)) return;
        if (lib_gmtime()-last > 8) if (lib_gmtime()%10==0) {
                idle = nntp_getidle2(&since);
/*imsg("Busy   %d %d\n",idle,since);*/
                if (since<=0) return;
                if (idle>since) return;
                /*if (since>idle) since = idle;*/
                dmsg("ncmd: Busy %d %d %d\n",(int) ((double) (since-idle) * 100.0)/since,idle,since);
                gstat_xadd("Busy %",(int) (((double) (since-idle) * 100.0)/since*10)); 
                gstat_xsend();
                last = lib_gmtime();
		if (last-now > 1) imsg("ncmd_background: Took %d seconds in gstat_xsend\n",last-now);
        }

        if (lib_gmtime()-wlast>500) {
                gstat_rewrite();
                wlast = lib_gmtime();
		if (wlast-now > 2) imsg("ncmd_background: Took %d seconds in rewrote gstat\n",wlast-now);
        }

}

/* New pident code 
	Do lookup immediately, and then when we get to post if no response then
	wait 1 second, try and read reply, and then giveup.

*/
static chan_ident[MAX_CHAN];
static chan_id_when[MAX_CHAN];
static chan_ident_chan[MAX_CHAN];
void id_background(void)
{
	int i;
	int nchan = chan_nchan();

	for (i=0; i<nchan; i++) {
		if (chan_ident[i]<0) continue;
		if (chan_id_when[i]<=0) continue;
		if (chan_id_when[i]<lib_gmtime()) {
			imsg("pidentd: Lookup timed out\n");
			id_close(i);
		}
	}
}
void id_ask(int chan)
{
        char rhost[BFSZ];
        char bf[BFSZ];
        int tout;
	int pidchan;
	int remoteport,localport;

        if (!chan_isopen(chan)) {
                imsg("pidentd failed because channel no longer open [%d], not serious\n",chan);
                return;
        }
        if (!nntp_getports(chan,&localport,&remoteport)) {
                imsg("pidentd: Get ports failed so couldn't do pidentd for chan %d\n",chan);
                return;
        }
        lprintf(rhost,BFSZ-1,"%s:113",chan_getipnum(chan)); /* 119 While testing, change to 113*/

	pidchan = chan_open(rhost);
	chan_ident[chan] = pidchan;
	chan_id_when[chan] = 0;

        imsg("ncmd: pidentd open {%s}, ask about %u, %u\n",rhost,(unsigned int) remoteport,(unsigned int) localport);
        if (pidchan<0) {
                imsg("pidentd: Channel open failed for {%s} (not serious) \n",rhost);
                return;
        }
	chan_ident_chan[pidchan] = chan;
        chan_setstate(pidchan,C_ID_READ);
        lprintf(bf,BFSZ-1,"%u , %u\r\n",(unsigned int) remoteport,(unsigned int) localport);
        dmsg("ncmd: pidentd request sent %s",bf);
        chan_write_s(pidchan,bf);       
        tout = atoi(config_get(C_IDENT_TIMEOUT));
        if (tout==0) tout = 3;
	chan_id_when[chan] = lib_gmtime() + tout;        
        chan_settimeout(pidchan,lib_gmtime()+tout+1);
}
int id_close(int chan)
{
	if (chan_id_when[chan]<=0) return TRUE;
	if (chan_ident[chan]<0) return TRUE;
	chan_ident_chan[chan_ident[chan]] = -1;
	chan_close(chan_ident[chan]);
	chan_ident[chan] = -1;
	chan_id_when[chan] = 0;
	return TRUE;
}
int id_check(int chan)
{	
	int r;

	if (chan_id_when[chan]<=0) return FALSE;
	if (chan_ident[chan]<=0) return FALSE;
	if (!chan_isopen(chan_ident[chan])) return FALSE;
	if (chan_id_when[chan]>=lib_gmtime()) {
		r = chan_waitfor(chan_ident[chan],500);
	} else {
		r = chan_waitfor(chan_ident[chan],5);
	}
	if (!r) {
		imsg("pidentd: id_check, no response so closing link\n");
		id_close(chan);
		return FALSE;
	}
	imsg("Calling channel_read for ident channel for %d %d\n",chan,chan_ident[chan]);
        channel_read(chan_ident[chan]);
	return TRUE;
}
void id_read(int chan, char *buf, int len)
{
        int rremote,rlocal;
        char orig[BFSZ];
        char *s;

        ncpy(orig,buf,BFSZ-1);
        
        s = strtok(buf,",: \t\015\012"); if (s==NULL) goto fail;
        rremote = atoi(s);
        s = strtok(NULL,",: \t\015\012"); if (s==NULL) goto fail;
        rlocal = atoi(s);
        s = strtok(NULL,": \t\015\012"); if (s==NULL) goto fail;
        if (strcmp(s,"USERID")!=0) goto fail;
        s = strtok(NULL,":\015\012"); if (s==NULL) goto fail;  /* Ignore the name of the operating system */
        s = strtok(NULL,":\015\012"); if (s==NULL) goto fail;
/*        if (rremote!=remoteport) {emsg("ident response for wrong port %s\n",orig); goto ret;}
        if (rlocal!=localport) {emsg("ident response for wrong port %s\n",orig); goto ret;}
*/
	if (chan_ident_chan[chan]>=0) chan_setident(chan_ident_chan[chan],s);
        dmsg("ncmd: pidentd Authenticated user for channel %d is {%s} \n",chan_ident_chan[chan],s);
        imsg("ncmd: pidentd Authenticated user for channel %d is {%s} \n",chan_ident_chan[chan],s);
ret:
	id_close(chan_ident_chan[chan]);
        return;
fail:
        imsg("pidentd: Invalid response from PIDENTD %s\n",orig);
	id_close(chan_ident_chan[chan]);
        goto ret;
}

#define PASSMAX 40
static char *pass_list[PASSMAX];
static int pass_time[PASSMAX];

int pass_fail_recent(char *s)
{
	int i;
	int tout = atoi(config_get(C_PASS_TIMEOUT));
	if (tout==0) tout = 4;
	if (config_true(C_PASS_NOLIMIT)) return FALSE;
	for (i=0; i<PASSMAX; i++) {
		if (pass_list[i]!=NULL) if (strcmp(pass_list[i],s)==0) {
			if ((lib_gmtime()-pass_time[i])<tout) {
				return TRUE;
			}
		}
	}
	return FALSE;
}
int pass_fail_add(char *s)
{
	int i;
	for (i=0; i<PASSMAX; i++) {
		if (pass_list[i]!=NULL) if (strcmp(pass_list[i],s)==0) {
			pass_time[i] = lib_gmtime();
			return TRUE;
		}
	}
	if (pass_list[PASSMAX-1]!=NULL) mystrfree(pass_list[PASSMAX-1],SD_NCMD);
	for (i=PASSMAX-1; i>0; i--) {
		pass_list[i] = pass_list[i-1];
		pass_time[i] = pass_time[i-1];
	}
	pass_list[0] = mystrdup(s,SD_NCMD);
	pass_time[0] = lib_gmtime();
	return TRUE;
}

typedef struct {int ngot; int upto; int failed; } Seq;
static Seq seq1[MAX_CHAN];
static Seq seq2[MAX_CHAN];
static Seq seq3[MAX_CHAN];
static int seq_matched[MAX_CHAN];

int seq_init(int c)
{
	seq1[c].failed = FALSE;
	seq1[c].upto = 0;
	seq1[c].ngot = 0;
	seq2[c].failed = FALSE;
	seq2[c].upto = 0;
	seq2[c].ngot = 0;
	seq3[c].failed = FALSE;
	seq3[c].upto = 0;
	seq3[c].ngot = 0;
	return TRUE;
}
int seq_check(Seq *seq, Slist *slist, int cmd, int hasargs);
int seq_log(char *username, char *ipname, char *ipnum);
int seq_next(int c, int cmd, int hasargs)
{
	if (c<0) return TRUE;
	if (c>=MAX_CHAN) return TRUE;
	if (!config_true(C_DETECT_SUCK)) return TRUE;
	if (seq_matched[c]) return TRUE;
	if (seq_check(&seq1[c],slist1,cmd,hasargs)) {
		seq_matched[c] = TRUE;
		emsg("User appears to be sucking news, (%.100s)\n",chan_getuser(c)); 
		seq_log(chan_getuser(c),chan_getipname(c),chan_getipnum(c));		
	}
	if (seq_check(&seq2[c],slist2,cmd,hasargs)) {
		seq_matched[c] = TRUE;
		emsg("User appears to be sucking news, (suck_stream) (%.100s)\n",chan_getuser(c)); 
		seq_log(chan_getuser(c),chan_getipname(c),chan_getipnum(c));		
	}
	return TRUE;
}
#define FILE_SEQ file_log("seq.log")
int seq_log(char *username, char *ipname, char *ipnum)
{
	FILE *f;
	f = fopen(FILE_SEQ,"a");
	if (f==NULL) return FALSE;
	fprintf(f,"%s %s %s %s\n",emsg_time(),username,ipname,ipnum);
	fclose(f);
	return TRUE;
}	
int seq_check(Seq *seq, Slist *slist, int cmd, int hasargs)
{
	int u;
	if (seq->failed) return FALSE;
again:
	u = seq->upto;

/*printf("seq check upto %d want %d got %d  end=%d\n",u,slist[u].cmd,cmd,S_END);*/
	if (slist[u].cmd==S_END) {
		return TRUE;
	}
	if (slist[u].cmd==cmd) {
		if (hasargs & (!slist[u].hasargs)) { goto failed;}
		if (!hasargs & (slist[u].hasargs)) { goto failed;}
		seq->ngot++;
		if (slist[u].rep==0) return FALSE;
		if (slist[u].rep == seq->ngot) {
			seq->upto++;
			seq->ngot=0;
			return FALSE;
		}
		goto failed;
	}
	if (seq->ngot==0) {
		goto failed;
	}
	seq->upto++;
	seq->ngot = 0;
	goto again;
failed:
	dmsg("sequence failed %d\n",cmd);
	seq->failed = TRUE;
	return FALSE;
}



/*

	suck stream

30 16:32:06: In  13 {mode reader
30 16:32:06: In  14 {group test.2
30 16:32:06: In  14 {stat 1188759
30 16:32:06: In  68 {stat 1188759
30 16:32:06: In  17 {article 1188759
30 16:32:06: In  17 {article 1188760
30 16:32:06: In  17 {article 1188761
30 16:32:06: In  17 {article 1188762
30 16:32:06: In  17 {article 1188763
30 16:32:06: In  17 {article 1188764
30 16:32:06: In  17 {article 1188765
30 16:32:06: In  17 {article 1188766
30 16:32:06: In  17 {article 1188767
30 16:32:06: In  17 {article 1188768
30 16:32:07: In  6 {next
30 16:32:07: In  6 {next
30 16:32:07: In  6 {next
30 16:32:07: In  6 {next
30 16:32:07: In  6 {next
30 16:32:07: In  6 {next
30 16:32:07: In  6 {next
30 16:32:07: In  6 {next
30 16:32:07: In  6 {next
30 16:32:07: In  6 {next
30 16:32:08: In  17 {article 1188769

	pull

30 16:35:32: In  13 {MODE READER
30 16:35:32: In  14 {GROUP test.2
30 16:35:32: In  14 {STAT 1187700
30 16:35:32: In  14 {STAT 1187701
30 16:35:47: In  6 {NEXT
30 16:35:47: In  9 {ARTICLE
30 16:35:48: In  6 {NEXT
30 16:35:48: In  9 {ARTICLE
30 16:35:48: In  6 {NEXT
...
*/


/*




ip username usergroup bytes cmd args
Example: 
10.0.0.1 myname mygroup 0 login 
10.0.0.1 myname mygroup 25 group alt.test
10.0.0.1 myname mygroup 25465 xover alt.test 1-500
10.0.0.1 myname mygroup 50343 article <msg23232@here.com>
10.0.0.1 myname mygroup 0 logout

*/
static int lastcheck[MAX_CHAN];
int ncmd_recheck(int chan)
{
	int t;/* Check if authentication module is still happy */

	if (strlen(config_get(C_PIPE_USE))==0) return TRUE;
	if (strlen(chan_getuser(chan))==0) return TRUE;
	t = time(NULL);
	if (t-lastcheck[chan] < (60*3)) return TRUE;
	imsg("Rechecking user is still allowed to download data (%s)\n",chan_getuser(chan));
	lastcheck[chan] = t;
	if (!user_recheck(chan)) {
		imsg("User %s is now disabled\n",chan_getuser(chan));
		return FALSE;
	}
	imsg("User %s is still ok\n",chan_getuser(chan));
	return TRUE;
}
int wide_read(int chan, char *xin, int sz);
static int wchan = -1;
static int wopen;
int ncmd_widearea_send(char *cmd)
{
	static int ch = -1;

	char bf[BFSZ];
	lprintf(bf,BFSZ-1,"fcmd %s %s\r\n",conf_getpass(),cmd);
	if (wchan<0) {
		if (lib_gmtime() - wopen < 60) return FALSE;
		imsg("wide: Trying to open link to widearea master %s\n",config_get(C_WIDEAREA_HOST));
	        wchan = nntp_open(config_get(C_WIDEAREA_HOST));		
		chan_setfn(wchan,wide_read);
		wopen = lib_gmtime();
	}
	if (!nntp_isopen(wchan)) {
		imsg("wide: Closing link to widearea system\n");
		nntp_close(wchan);
		wchan = -1;
		return FALSE;
	}
	imsg("wide: Sending %s\n",cmd);
	chan_write_s(wchan,bf);
        return TRUE;
}

int wide_read(int chan, char *xin, int sz)
{
        char in[BFSZ];
        char *s;
	int count;
	int c;
	char host[BFSZ];
        if (sz>=200) sz = 200-1;

	if (sz<0) {
		imsg("wide: wchan closed by remote end\n");
		nntp_close(wchan);
		wchan = -1;
		return FALSE;
	}
        ncpy(in,xin,sz);
        s = strchr(in,'\r'); if (s!=NULL) *s = 0;
        s = strchr(in,'\n'); if (s!=NULL) *s = 0;
        imsg("wide: Response (%s)\n",in);


	if (strncmp(in,"wrong_host=",11)==0) {
		count = atoi(in+11);
		s = strstr(in,"chan="); if (s==NULL) return TRUE;
		c = -1; ncpy(host,"",10);
		c = atoi(s+5); 
		s = strstr(in,"on="); if (s==NULL) return TRUE;
		ncpy(host,s+3,BFSZ-1);
		if (chan_wronghost[c] == NULL) chan_wronghost[c] = mystrdup(host,SD_NCMD);
		imsg("wide: Will log off user=%.100s channel %d, host %.100s\n",chan_getuser(c),c,host);
	} else if (strncmp(in,"count=",6)==0) {
		return TRUE;
	} else {
		imsg("wide: Ignoring response from wide master %s\n",in);
	}

        return TRUE;
}

char *item_last_mid(void);

int ncmd_pipe(int chan, int bytes, char *cmd)
{
/*	Store usergroup info in channel data. */
	int g = chan_getgrp(chan);
	char user[BFSZ];
	char bf[BFSZ];
	char gname[BFSZ];
	char mid[BFSZ];
	char host[BFSZ];


	if (strlen(config_get(C_WIDEAREA_HOST))!=0) {
		ncpy(host,config_get(C_WIDEAREA_MYNAME),BFSZ-1);
		if (strlen(host)==0) ncpy(host,nntp_hostip(),BFSZ-1);
		
		if (strlen(chan_getuser(chan))>0) {
			if (strcmp(cmd,"login")==0) {
				lprintf(bf,BFSZ-1,"mcount_login %d %s %s %s",chan,chan_getuser(chan),host,chan_getipnum(chan));
				ncmd_widearea_send(bf);
			}
			if (strcmp(cmd,"logout")==0) {
				lprintf(bf,BFSZ-1,"mcount_logout %s %s",chan_getuser(chan),host);
				ncmd_widearea_send(bf);
			}
		}
	}

	if (strlen(config_get(C_PIPE_USE))==0) return TRUE;
	ncpy(user,chan_getuser(chan),BFSZ-1);
	strcpy(gname,"None");
	if (strcmp(cmd,"login")==0) lastcheck[chan] = time(NULL);
	strcpy(mid,"");
	if (g>0) ncpy(gname,group_name(g),100);
	if (strcmp(cmd,"post")==0) {
		ncpy(gname,post_this_mid(),100);
	}
	if (strlen(user)==0) return TRUE;
	lprintf(bf,400,"%s\t%s\t%s\t%s\t%d\t%s\t%s",
		chan_getipnum(chan),chan_getipname(chan),chan_getuser(chan),chan_getusergroups(chan),bytes,cmd,gname);
	imsg("pipe_use: %s\n",bf);
	strcat(bf,"\n");
	ncmd_pipe_write(bf);
	return TRUE;
}

static FILE *fpipe;
int ncmd_pipe_write(char *bf)
{

	if (fpipe==NULL) fpipe = popen(config_get(C_PIPE_USE),"w");
	if (fpipe==NULL) {
		filemsg("popen (%s)\n",config_get(C_PIPE_USE));
		return FALSE;
	}
	fprintf(fpipe,"%s",bf);
	fflush(fpipe);
	return TRUE;
}
int ncmd_cleanup(void)
{
	if (fpipe!=NULL) {
		fprintf(fpipe,"exit\n");
		fflush(fpipe);
		pclose(fpipe);
	}
	fpipe = NULL;
	return TRUE;
}