/*
d:/surgemail/mail.surgeweb.com//ha/uj/ralph/mdir/new/1064890108.2504_3.mailsvc1~2,(1416,108)[1064915
  Lets try and detect sucking 

	suck 

mode reader
stat nnn
(1...n)
article
next
article 
next (no parameters)
fprintf
	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
...




Readonly servers detect new items by:
        Scanning active.dat
        Checking length of history file
        slave_fixgroup(), sends  xactive group,from,to,range,flags
*/

/* Copyright (C) 1995 NetWin World, New Zealand, All rights reserved. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "cmd.h"
#ifdef NeXT
#include <sys/time.h>
#endif 
#include "group.h"
#include "item.h"
#include "dnews.h"
#include "nntp.h"
#include "hist.h"
#include "suck.h"
#include "chan.h"
#include "sary.h"
#include "str.h"
#include "lib.h"
#include "db.h"
#include "lib_spawn.h"
#include "feed.h"
#include "expire.h"
#include "ncmd.h"
#include "ftslib.h"
#include "domain.h"
#include "pull.h"
#include "ml.h"
#ifdef KEYLIB
#include "keylib.h"
#endif
int fts_reload(void);

int db_reindex(void);
void db_set_rebuild(int t);
int cmd_showgroup_hist(int chan, char *gname);

int test_strings(void);
int suck_reindex(void);
int ncmd_xdfind(int chan, char *in);
void stats_clear(void);
int db_sort_start(void);
void ncmd_body_finish(int chan);
void ncmd_list_finish(int chan);
void cmd_pause_ihave(int t, char *reason);
void ncmd_list_names_finish(int chan);
int test_strings_lots_n(int n, int sz);
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 copy_item(int chan,char *gfrom,char *gto, int n);
int thin_setdebug(void);
void copy_group(int chan,char *gfrom,char *gto, int newmid);
void p_compress_between(void);
int g_group_read(int grp);
void test_stack(void);
void set_failmid(int t);
int db_test_idx(int chan, int grp, int num);
void debug_index_show(char *gname);
extern int key_expired;
int cmd_iteminfo(int chan, char *p1, char *p2);
char *hcache_status(void);
void spam_rej_show(int chan);

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);
char *get_reason(void);
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 is_master(void);
void make_cancel(int grp, int item);

void spam_crc_age(void);
void spam_from_age(void);
int pgp_verify(String *xitem, char *issuer);
void cmd_extract(int chan, char *groups);
void too_old_off(void);
void slave_buildxover(int chan, char *in);

static int speed_test,xspeed_test;


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

static char *art_fail;
String *stats_show_in(void);
void tomail_show(int chan);
void slave_xdpost(int schan, char *in);
static int xactive_disable;
String *stats_groups_read(char *s, int chan);
void ncmd_reload(void);
int mcount_logout(int chan,char *p1,char *p2);
int mcount_login(int chan,char *pchan, char *p1,char *p2, char *userip);


void cmd_showfile(int chan, char *fname);
void cmd_emsg_tail(int chan, char *lines);
void slave_newgroup(int xchan, char *in);
void g_free_all(char *reason);
int db_check_index(void);
int post_article(int chan,String *anitem, char *s);
void db_check_group(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 int chan_postchan[MAX_CHAN];
static char chan_postslave[MAX_CHAN];
int spawn_exit(void);
void convert_dnewsfmt(void);
void nntp_measure_idle(int x);
void hcache_rebuild(void);
int scan_missing;
int do_cmd2(int chan, char *cmd, char *p1);

int ml_blank_file(void);

int ncmd_login(int chan, char *in)
{
	char bf[BFSZ];
	lprintf(bf,BFSZ-1,"388 %d is hash code\015\012",chan_gethash(chan));
        chan_write_s(chan,bf);
	return TRUE;
}
void part_showindex(int chan);
int telllog(char *ip, char *rest)
{
	FILE *f;
	char fname[BFSZ];
	ncpy(fname,file_log("tell.log"),BFSZ-1);
	f = fopen(fname,"a");
	if (f==NULL) return FALSE;
	fprintf(f,"%s %s %s\n",emsg_time(),ip,rest);
	fclose(f);
	if (lib_length_file(fname)>1000000) remove(fname);
	return TRUE;
}
int ncmd_cmd(int chan, char *in)
{       
        int i,j;
        int ndone = 0;
        int f,g;
        char key_info[BFSZ];
        String *xstring,*xitem;
        int suckn;
        int n;
	int max;
        int num,grp,from,to;
        int canpost,canread,canihave,isauth;
        char r1[BFSZ2];
        char r2[BFSZ2];
        char *isdel;
        static int nguess;
        char pass[BFSZ],bf[BFSZ],bf2[BFSZ];
        char cmd[BFSZ],p1[BFSZ],p2[BFSZ],p3[BFSZ],p4[BFSZ];
        char rest[BFSZ];
        char tmp[BFSZ];
	char reason[BFSZ];
        grp = -1;
        strcpy(key_info,"");
        ncpy(cmd,get_word(in,2),99) ;   
        strlwr(cmd); 
        ncpy(pass,get_word(in,1),99) ;  
        ncpy(rest,get_rest(in,2),99);
        imsg("Manager Command (fcmd PASSWORD %s)\n",rest);



	if (!conf_pass_try(pass,chan,FALSE,FALSE,reason)) {
                lprintf(tmp,BFSZ-1,"481 Cannot access tellnews.pass, check dnews.conf and file protections %s\015\012",reason);
                chan_write_s(chan,tmp);
                emsg("ncmd: Password failure {%s} from %s does not match tellnews.pass %s\n",pass,chan_getipname(chan),reason);
		telllog(chan_getipname(chan),"tellnews.pass failure from this host");
                goto ret;
        } 
	telllog(chan_getipname(chan),rest);
        ncpy(p1,get_word(in,3),99) ;    
        ncpy(p2,get_word(in,4),99) ;    
        ncpy(p3,get_word(in,5),99) ;    
        ncpy(p4,get_word(in,6),99) ;    

        dnews_getkey(bf);
#ifdef KEYLIB
	if (keylib_istemp(bf)) {
#else
        if (strlen(bf)!=0) {
#endif
                if (strcmp(cmd,"admin_tool")!=0) 
                if (strcmp(cmd,"key")!=0) {
                        lprintf(bf2,BFSZ-1,"483 %s\015\012",bf); chan_write_s(chan,bf2);
                        strcpy(key_info,bf2);
#ifdef KEYLIB
                        lprintf(bf,BFSZ-1,"582 - See http://netwinsite.com/activate.htm for activation instructions\015\012");
                        chan_write_s(chan,bf);
                        if (keylib_expired()) {
                                chan_write_s(chan,"483 The command (tellnews activate Nxxx_extend) will enable dnews for a few more days\015\012");
                                chan_write_s(chan,"483 where 'Nxxx' is your registration number\015\012");
                        }
#endif
                }
        }
	if (strcmp(cmd,"admin_tool")==0) {
		/* Allow reading/writing of news groups */
		chan_allow(chan);
		return TRUE;
	}
	if (strcmp(cmd,"cancel")==0) {
	    if (*p1 == '<') {
		if (!hist_find(p1,&grp,&from)) {
                   lprintf(bf,BFSZ-1,"\tItem not found {%s}\015\012",p1);
                   chan_write_s(chan,bf);
                   goto ret;                 
                }
            } else {
		strlwr(p1);
                grp = group_find(p1);
                from = atoi(p2);
            }
            if (grp<0) {
                lprintf(bf,BFSZ-1,"\tItem not found {%s}\015\012",p1);
	        chan_write_s(chan,bf);
	        goto ret;                 
            }
            lprintf(bf,BFSZ-1,"\tCancelling item %d from group %s\015\012",from,group_name(grp));
	    chan_write_s(chan,bf);
	    item_cancel(chan,grp,from);
	} else if (strcmp(cmd,"mcount_login")==0) {
		mcount_login(chan,p1,p2,p3,p4);
		return TRUE; /* Don't send 281 response */
	} else if (strcmp(cmd,"mcount_reset")==0) {
		mcount_reset();
		lprintf(bf,BFSZ-1,"\tClearing mcount tables for widearea master\015\012");
		chan_write_s(chan,bf);
		goto ret;
	} else if (strcmp(cmd,"mcount_logout")==0) {
		mcount_logout(chan,p1,p2);
		return TRUE; /* Don't send 281 response */
	} else if (strcmp(cmd,"update")==0) {
                i = atoi(p1);
                if (i>0) i--;
                lprintf(bf2,BFSZ-1,"update %d",i);
                cmdq_add(bf2);
                goto ret;
        } else if (strcmp(cmd,"pull_getold")==0) {
		chan_write_s(chan,"Pull getold \r\n");
           for (i=0; i<group_n(); i++) {
            if (group_match(p1,group_namelc(i))) {
		    int f;
              for (f=0; f<4; f++) {
		      imsg("pull_getold (%s) %s\n",group_name(i),p2);
                pull_getold(i,f,atoi(p2));
              }
            }
          }
         } else if (strcmp(cmd,"getold")==0) {
          if (strlen(p2)==0) strcpy(p2,"10");
          for (i=0; i<group_n(); i++) {
            if (group_match(p1,group_namelc(i))) {
              for (f=0; f<4; f++) {
                group_setfeedend(i,f,group_feedend(i,f)-atoi(p2));
                if (atoi(p2)>100) group_setfeedend(i,f,0);
              }
            }
          }
        } else if (strcmp(cmd,"test_dofree")==0) {
                /*mem_dofree(NULL,1234,"fromthisline",23);*/
        } else if (strcmp(cmd,"test_patch")==0) {
		item_xover_patchend(group_find(p1));
        } else if (strcmp(cmd,"expire_spool")==0) {
/*		if (atoi(p1)==0) {
	                chan_write_s(chan,"  You must specify a small number 1,2,3,4...\r\n");			
		} else {
			db_expire_spool(chan,atoi(p1));
		}
		*/
	} else if (strcmp(cmd,"test_parts")==0) {
		int r;
		char xsubj[BFSZ];
		char fname[BFSZ];
		char orig[BFSZ];
		int parti,partmax;
		ncpy(orig,get_rest(in,3),BFSZ-1);
		lprintf(bf,BFSZ-1,"test: %s\n",orig);
		chan_write_s(chan,bf);        
		r = parts_subject(orig,xsubj,fname,&parti,&partmax);
		lprintf(bf,BFSZ-1,"r=%d, %d/%d\n",r,parti,partmax);
		chan_write_s(chan,bf);        
	} else if (strcmp(cmd,"ml_login")==0) {
		ml_login(atoi(p1),p2,p3,2,4,100,0,bf,TRUE);
		lprintf(bf2,BFSZ-1,"ml: %s\n",bf);
		chan_write_s(chan,bf2);
	} else if (strcmp(cmd,"ml_add")==0) {
		ml_add(atoi(p1),1,atoi(p2),bf);
		lprintf(bf2,BFSZ-1,"ml: %s\n",bf);
		chan_write_s(chan,bf2);
	} else if (strcmp(cmd,"ml_clear")==0) {
		ml_clear(chan,p1,reason);
		lprintf(bf2,BFSZ-1,"ml: clearing (%s) %s\n",p1,reason);
		chan_write_s(chan,bf2);
	} else if (strcmp(cmd,"ml_logout")==0) {
		ml_logout(atoi(p1));
	} else if (strcmp(cmd,"ml_flush")==0) {
		ml_flush();
	} else if (strcmp(cmd,"pull_die")==0) {
		pull_die();
	} else if (strcmp(cmd,"ml_newday")==0) {
		ml_do_newday();
	} else if (strcmp(cmd,"ml_blank")==0) {
		ml_blank_file();
	} else if (strcmp(cmd,"ml_show")==0) {
		ml_show_areas(chan,FALSE);
	} else if (strcmp(cmd,"ml_test")==0) {
		chan_setipnum(chan,p1);
		chan_setipname(chan,p1);
		ml_add(chan,10,atoi(p2),reason);
		lprintf(bf,BFSZ-1,"ml_test %s %s %s\n",p1,p2,reason);
		chan_write_s(chan,bf);
	} else if (strcmp(cmd,"show_dir")==0) {
		g_show_dir(chan);
	} else if (strcmp(cmd,"ml_showall")==0) {
		ml_show_areas(chan,TRUE);
	} else if (strcmp(cmd,"ml_debug")==0) {
		ml_show(chan);
		chan_write_s(chan,"ml_login chan user area, ml_read chan bytes, ml_logout chan, ml_flush, ml_newday, ml_show, ml_showall, ml_debug\r\n");
	} else if (strcmp(cmd,"bucket_sort")==0) {
                bsort_que_add(atoi(p1));
        } else if (strcmp(cmd,"buckets_sort")==0) {
                bsort_que_add_all();
        } else if (strcmp(cmd,"pile_sort")==0) {
                bsort_que_add_pile(atoi(p1));
        } else if (strcmp(cmd,"debug_group")==0) {
                debug_index_show(p1);
        } /*else if (strcmp(cmd,"test_score")==0) {
                score_test();
        }*/ else if (strcmp(cmd,"test_access")==0) {
                access_check(p1,p1,p2,p3,r1,r2,&canpost,&canread,&canihave,&isauth);
                lprintf(bf,BFSZ-1,"\tDomain (%s) User (%s) Password (%s)\r\n",p1,p2,p3);  chan_write_s(chan,bf);
                lprintf(bf,BFSZ-1,"\tRead Groups (%s) (%s)\r\n",canread ? "true" : "false" ,r2);  chan_write_s(chan,bf);
                lprintf(bf,BFSZ-1,"\tPost Groups (%s) (%s)\r\n",canpost ? "true" : "false" ,r1);  chan_write_s(chan,bf);
                lprintf(bf,BFSZ-1,"\t%s\n\t%s \r\n",canihave ? "Can Ihave" : "No Ihave" ,isauth ? "Authrized Access OK" : "NOT Authorized"  );  chan_write_s(chan,bf);
        } else if (strcmp(cmd,"export_active")==0) {
                chan_write_s(chan,"Writing INN format active file to active.inn\r\n"); 
                group_export();
        } else if (strcmp(cmd,"diskuse")==0) {
                if (strlen(p1)==0) strcpy(p1,"*");
                num = cmd_du(chan,&grp,p1,&f);
                lprintf(bf,BFSZ-1,"%d groups matched {%s} total %dk bytes %d articles\015\012",grp,p1,num/10,f);
                chan_write_s(chan,bf);
                goto ret;
        } else if (strcmp(cmd,"diskuse_all")==0) {
                num = cmd_du_all(chan);
		goto ret;
        } else if (strcmp(cmd,"getgroups")==0) {
                if (strlen(p1)>2) {
                        lprintf(bf,BFSZ-1,"getactive %s",p1);
                        cmdq_add(bf);
                        lprintf(bf,BFSZ-1,"Queueing active file update from host (%s)\015\012",p1);
                        chan_write_s(chan,bf);
                        goto ret;
                }
                
                suckn = atoi(p1) - 1;
                if (suckn<0) suckn = 0;
                lprintf(bf,BFSZ-1,"getgroups %d",suckn);
                cmdq_add(bf);
                lprintf(bf,BFSZ-1,"Queueing active file update for suck %d\015\012",suckn+1);
                chan_write_s(chan,bf);
                goto ret;
        } else if (strcmp(cmd,"trimgroups")==0) {
                suckn = atoi(p1) - 1;
                if (suckn<0) suckn = 0;
                lprintf(bf,BFSZ-1,"trimgroups %d",suckn);
		suck_set_trimonly(TRUE);
                cmdq_add(bf);
                lprintf(bf,BFSZ-1,"Queueing active file matchgroups for suck %d\015\012",suckn+1);
                chan_write_s(chan,bf);
                goto ret;
        } else if (strcmp(cmd,"matchgroups")==0) {
                suckn = atoi(p1) - 1;
                if (suckn<0) suckn = 0;
                lprintf(bf,BFSZ-1,"trimgroups %d",suckn);
                cmdq_add(bf);
                lprintf(bf,BFSZ-1,"Queueing active file matchgroups for suck %d\015\012",suckn+1);
                chan_write_s(chan,bf);
                goto ret;
        } else if (strcmp(cmd,"matchsite")==0) {
                lprintf(bf,BFSZ-1,"matchsite %s %s",p1,p2);
		if (strlen(p2)==0) {
			chan_write_s(chan,"You must specify the site and the groups or *\r\n, e.g.  tellnews matchsite msnews.microsoft.com microsoft.*\r\n");
			goto ret;
		}
                cmdq_add(bf);
                lprintf(bf,BFSZ-1,"Queueing matchsite site=%s groups=%s\015\012",p1,p2);
                chan_write_s(chan,bf);
                goto ret;
        } else if (strcmp(cmd,"trimfile")==0) {
                trim_file(chan,p1);
                goto ret;
        } else if (strcmp(cmd,"sendlogx")==0) {
                chan_write_s(chan,"ok\r\n");
                chan_addlog(chan);
                goto ret;
        } else if (strcmp(cmd,"matchfile")==0) {
                trim_file(chan,p1);
                goto ret;
        } else if (strcmp(cmd,"matchmod")==0) { /* Fix groups so moderated flags are right.*/
		trim_fix_mod(TRUE);
                trim_file(chan,p1);
		trim_fix_mod(FALSE);
                goto ret;
        } else if (strcmp(cmd,"checkgroups")==0) {
                chan_write_s(chan,"You can use tellnews undelete \"group.names.*\" to undo any unwanted deletes\r\n");
                if (strlen(p1)==0) {
                        chan_write_s(chan,"  You must specify the checkgroups file\r\n");
                } else control_check(chan,p1);
                goto ret;
        } else if (strcmp(cmd,"remove_tree")==0) {
                remove_tree(chan);
                goto ret;
        } else if (strcmp(cmd,"purge_one")==0) {
                db_purge_test(atoi(p1));
                goto ret;
        } else if (strcmp(cmd,"purge_scan")==0) {
                db_purge_scan_test(atoi(p1));
                goto ret;
        } else if (strcmp(cmd,"part_showindex")==0) {
	        part_showindex(chan);
                goto ret;
        } else if (strcmp(cmd,"part_test")==0) {
		int t,part,partmax;
		char subj[BFSZ];
		char fname[BFSZ];

		t = parts_subject(get_rest2(in,3), subj, fname, &part, &partmax);
		lprintf(bf,200,"%d/%d  %d\r\n",part,partmax,t);
		chan_write_s(chan,bf);
                goto ret;
	} else if (strcmp(cmd,"test_parsedate")==0) {
		time_t t;
		struct tm atm;
		struct tm *tt;
		char tmpstr[BFSZ];
                strcpy(p1,get_rest2(in,3));
		t = (time_t) parsedate(p1);
		tt = safe_localtime(&t,&atm);
		lprintf(bf,BFSZ-1,"Time (%s) %d {%s} \r\n",p1,t,safe_asctime(tt,tmpstr));
		chan_write_s(chan,bf);
		goto ret;
        } else if (strcmp(cmd,"purge_items")==0) {
                db_purge_items();
                goto ret;
        } else if (strcmp(cmd,"disconnect")==0) {
                chan_close(atoi(p1));
                goto ret;
        } else if (strcmp(cmd,"show_filter")==0) {
                spam_rej_show(chan);
                goto ret;
        } else if (strcmp(cmd,"show_pf")==0) {
                pf_show(chan);
                goto ret;
        } else if (strcmp(cmd,"update_clear")==0) {
                suck_update_clear();
		imsgf("Pull clear\n");
		pull_clear();
                chan_write_s(chan,"Cleared update queue\015\012");
                goto ret;
        } else if (strcmp(cmd,"update2")==0) {
                cmdq_add("update 1");
                goto ret;
        } else if (strcmp(cmd,"update3")==0) {
                cmdq_add("update 2");
                goto ret;
        } else if (strcmp(cmd,"update4")==0) {
                cmdq_add("update 3");
                goto ret;
        } else if (strcmp(cmd,"updateone")==0) {
                suckn = atoi(p2)-1;
                if (suckn<0) suckn = 0;
                for (i=0; i<group_n(); i++) if (group_iscached(i)) {
                        if (group_match(suck_getgroups(suckn),group_name(i))) {
                                if (group_match(p1,group_name(i))) {
	        			if (pull_true(suckn)) {
						pull_add(suckn,i,FALSE);
						lprintf(bf2,BFSZ-1,"Queueing %s from pull%d \015\012",group_name(i),suckn+1);
	                                        chan_write_s(chan,bf2);
					} else {
						lprintf(bf2,BFSZ-1,"Queueing %s from suck%d \015\012",group_name(i),suckn+1);
	                                        chan_write_s(chan,bf2);
	                                        suck_queue_feed_pri(group_name(i),1,suckn);
	                                        group_setcatchup(i,group_feedend(i,suckn),suckn);
	                                }
				}
                        }
                }
                goto ret;
        } else if (strcmp(cmd,"delover")==0) {
                suckn = atoi(p1);
                n = 0;
                if (suckn==0) goto ret;
                for (i=0; i<group_n(); i++) {
                        if (group_getend(i)>suckn) {
                                group_delete(i);
                                group_setend(i,1000);
                                group_setstart(i,1001);
                                group_setrange(i,0);
                                n++;
                        }
                }
                lprintf(bf,BFSZ-1,"Deleted %d Groups\r\n",n);
                chan_write_s(chan,bf);
                goto ret;
        } else if (strcmp(cmd,"showover")==0) {
                suckn = atoi(p1);
                for (i=0; i<group_n(); i++) {
                        if (group_getend(i)>suckn) {
                                lprintf(bf,BFSZ-1,"Group %s %d \r\n",group_name(i),group_getend(i));
                                chan_write_s(chan,bf);
                        }
                }
                goto ret;
        } else if (strcmp(cmd,"showcached")==0) {
                if (strlen(p1)==0) strcpy(p1,"*");
                for (i=0,ndone=0; i<group_n() && ndone<1000; i++) if (group_iscached(i)) {
                        if (group_match(p1,group_name(i))) {
                                ndone++;
                                isdel = "";
                                if (group_isdeleted(i)) isdel = "(Del) ";
                                lprintf(bf2,BFSZ-1,"%s%s start %d end %d, upto on feed %d, days %d \015\012",isdel,group_name(i),group_getstart(i),group_getend(i),group_feedend(i,0),group_getlife(i));
                                chan_write_s(chan,bf2);
                        }
                        if (ndone>=1000) chan_write_s(chan,"Only first 1000 shown\015\012");
                }
                goto ret;
        } else if (strcmp(cmd,"showgroup")==0) {
                cmd_showgroup(chan,p1,p2,p3);
                chan_write_s(chan," Usage: showgroup group.name [yyyy/mm/dd yyyy/mm/dd]\n");
        } else if (strcmp(cmd,"showgroup_hist")==0) {
                cmd_showgroup_hist(chan,p1);
        } else if (strcmp(cmd,"spam_postshow")==0) {
                chan_write_s(chan,spam_postshow());
                
        } else if (strcmp(cmd,"spam_postclear")==0) {
                spam_postclear();

        } else if (strcmp(cmd,"showgroups")==0) {
                if (strlen(p1)==0) strcpy(p1,"*");
		if (config_true(C_SHOW_ALL_OUTPUT)) max=1000000; else max=999;
                for (i=0,ndone=0; i<group_n() && ndone<max; i++) {
                        if (group_match(p1,group_name(i))) {
                                ndone++;
                                isdel = "";
                                if (group_isdeleted(i)) isdel = "(Del) ";
                                lprintf(bf2,BFSZ-1,"[%d:%d] %s %s start=%d end=%d items=%d, upto on feed %d, days %d Pile %d %d xov=%d %s\015\012",
                                        i,group_getgroupid(i),isdel,group_name(i),group_getstart(i),group_getend(i),group_getrange(i),group_feedend(i,0),group_getlife(i),gage_get(i),gage_hash(i),group_isxover(i),group_ismod(i) ? "Moderated" : "Normal");
                                chan_write_s(chan,bf2);
                        }
                        if (ndone>=max) {
				lprintf(bf,BFSZ-1,"Only first %d shown\015\012",ndone);
				chan_write_s(chan,bf);
			}
                }

                goto ret;
        } else if (strcmp(cmd,"morethan")==0) {
                if (strlen(p2)==0) strcpy(p2,"*");
                for (i=0,ndone=0; i<group_n() && ndone<1000; i++) {
			if (group_rawrange(i)>atoi(p1))
                        if (group_match(p2,group_name(i))) {
                                ndone++;
                                isdel = "";
                                if (group_isdeleted(i)) isdel = "(Del) ";
                                lprintf(bf2,BFSZ-1,"[%d:%d] %s %s start=%d end=%d items=%d, upto on feed %d, days %d Pile %d %d\015\012",
                                        i,group_getgroupid(i),isdel,group_name(i),group_getstart(i),group_getend(i),group_getrange(i),group_feedend(i,0),group_getlife(i),gage_get(i),gage_hash(i));
                                chan_write_s(chan,bf2);
                        }
                }

                goto ret;
        } else if (strcmp(cmd,"spawn_exit")==0) {
		spawn_exit();
        } else if (strcmp(cmd,"reload_fts")==0) {
                fts_reload();
        } else if (strcmp(cmd,"accept")==0) {
		nntp_accept(atoi(p1),p2,p3);
		goto ret;
        } else if (strcmp(cmd,"channel_read")==0) {
                channel_read(atoi(p1));
        } else if (strcmp(cmd,"reload_access")==0) {
               imsgf("Reload access.conf\n");
               access_read();
               chan_write_s(chan," access.conf reloaded\015\012");
        } else if (strcmp(cmd,"reload")==0) {
                ncmd_reload();
                imsgf("Reload slaves\n");
                slave_reload();
                expire_load();
                chan_write_s(chan," dnews.conf, newsfeeds.conf, access.conf, control.conf, expire.conf, nocem.conf reloaded \015\012");
		user_cache_free();
		nocem_load();
		nntp_set_welcome(config_get(C_WELCOME));
		big_setgrow(atoi(config_get(C_BIG_NGROW)));
		dom_reload();
		pull_init();
		nntp_set_small_fast(atoi(config_get(C_SMALL_FAST)));
		nntp_set_small_speed(atoi(config_get(C_SMALL_SPEED)));
        } else if (strcmp(cmd,"shutdown")==0) {
                goto doexit;
        } else if (strcmp(cmd,"exit")==0) {
doexit:         if (is_slave()) chan_write_s(chan,"SLAVE EXITING, USE:\r\n    tellnews -s 0 exit \r\n to shutdown dmulti and slaves.\r\n");
                wmsgf("ncmd: Exit requested, flushing files\n");
		chan_cleanup(chan);
                slave_shutdown();
                dnews_ex("Exit command");
                exit(1);
        } else if (strcmp(cmd,"password")==0) {
                conf_setapass(p1);
        } else if (strcmp(cmd,"showusers")==0) {
                user_report(chan);
        } else if (strcmp(cmd,"group_write")==0) {
                group_write();
        } else if (strcmp(cmd,"showcon")==0) {
                lprintf(bf,BFSZ-1,"%s\r\n",chan_showcon());
                chan_write_s(chan,bf);
        } else if (strcmp(cmd,"users_clear")==0) {
                user_clear();
		pop_cache_freeall();
        } else if (strcmp(cmd,"users_clearone")==0) {
                user_clear();
		i = user_cache_free_single(p1);
		j = pop_cache_freeone(p1);
		if (i | j) {
                   lprintf(bf,BFSZ-1,"User (%s) removed from cache\015\012",p1);                   chan_write_s(chan,bf);	
		} else {
                   lprintf(bf,BFSZ-1,"User (%s) not in cache\015\012",p1);                   chan_write_s(chan,bf);	
		}
        } else if (strcmp(cmd,"memlog")==0) {
                mem_blocks_show();
        } else if (strcmp(cmd,"cacheall")==0) {
                group_cacheall(atoi(p1));
#ifdef MEMDEBUG
        } else if (strcmp(cmd,"memdebug_test")==0) {
                mem_check();
        } else if (strcmp(cmd,"memdebug_die")==0) {
                mem_kill();
#endif
        } else if (strcmp(cmd,"reject")==0) {
                if (!hist_find(p1,&grp,&num)) {
                        hist_add(p1,"1-Jan-95 1:01:01 GMT","","");
                   lprintf(bf,BFSZ-1,"Item will be refused in future {%s}\015\012",p1);
                   chan_write_s(chan,bf);
                } else {
                   lprintf(bf,BFSZ-1,"Item already in history file {%s}\015\012",p1);
                   chan_write_s(chan,bf);
                }
        } else if (strcmp(cmd,"killslaves")==0) {
                slave_killslaves();
        } else if (strcmp(cmd,"killitem")==0) {
                if (*p1 == '<') {
                 if (!hist_find(p1,&grp,&from)) {
                   lprintf(bf,BFSZ-1,"Item not found {%s}\015\012",p1);
                   chan_write_s(chan,bf);
                   goto ret;                 
                 }
                } else {
                        strlwr(p1);
                        grp = group_find(p1);
                        from = atoi(p2);
                }
                if (grp<0) {
                   lprintf(bf,BFSZ-1,"Item not found {%s}\015\012",p1);
                   chan_write_s(chan,bf);
                   goto ret;                 
                }
                lprintf(bf,BFSZ-1,"Deleting item %d from group %s\015\012",from,group_name(grp));
                chan_write_s(chan,bf);
                item_delete_all(chan,grp,from);
                db_clear_cache("killitem");
                db_cancel_start();
		slave_flush(TRUE);
		slave_flush(FALSE);
        } else if (strcmp(cmd,"delete_items")==0) {
                strlwr(p1);
                grp = group_find(p1);
                from = atoi(p2);
		to = atoi(p3);
                if (grp<0) {
                   lprintf(bf,BFSZ-1,"Item not found {%s}\015\012",p1);
                   chan_write_s(chan,bf);
		   lprintf(bf,BFSZ-1,"Usage: tellnews delete_items group.name from to\r\n");
                   chan_write_s(chan,bf);
                   goto ret;                 
                }
                lprintf(bf,BFSZ-1,"Deleting items %d-%d from group %s\015\012",from,to,group_name(grp));
                chan_write_s(chan,bf);
                item_delete_range(chan,grp,from,to);
                db_clear_cache("delete_items");
                db_cancel_start();
		slave_flush(TRUE);
		slave_flush(FALSE);
        } else if (strcmp(cmd,"showinfo")==0) {
                cmd_iteminfo(chan,p1,p2);
        } else if (strcmp(cmd,"rules_reload")==0) {
                cmd_rules_reload(chan);
        } else if (strcmp(cmd,"rules_trace")==0) {
                cmd_rules_trace(chan,p1,p2);
        } else if (strcmp(cmd,"rules_group")==0) {
                cmd_rules_group(chan,p1,p2);
        } else if (strcmp(cmd,"iteminfo")==0) {
                cmd_iteminfo(chan,p1,p2);
        } else if (strcmp(cmd,"pull_status")==0) {
		pull_link_status(chan);
	} else if (strcmp(cmd,"status_hcache")==0) {
                chan_write_s(chan,hcache_status());
        } else if (strcmp(cmd,"report_limit")==0) {
                chan_write(chan,ml_report_show());
        } else if (strcmp(cmd,"hcache_status")==0) {
                chan_write_s(chan,hcache_status());
        } else if (strcmp(cmd,"rebuild_hcache")==0) {
                hcache_rebuild();
        } else if (strcmp(cmd,"histforget")==0) {
                if (hist_forget(p1)) {
                   chan_write_s(chan,"Item temporarily removed from history (so it will be accepted next time it is offered)\r\n");
                } else {
                   chan_write_s(chan,"Item NOT FOUND\r\n");
                }
        } else if (strcmp(cmd,"cancelitem")==0) {
                 if (!hist_find(p1,&grp,&from)) {
                   lprintf(bf,BFSZ-1,"Item not found {%s}\015\012",p1);
                   chan_write_s(chan,bf);
                   goto ret;                 
                 }
                if (grp<0) {
                   lprintf(bf,BFSZ-1,"Item not found {%s}\015\012",p1);
                   chan_write_s(chan,bf);
                   goto ret;                 
                }
                lprintf(bf,BFSZ-1,"Generating cancel for item %d from group %s\015\012",from,group_name(grp));
                chan_write_s(chan,bf);
                make_cancel(grp,from);
        } else if (strcmp(cmd,"killone")==0) {
                strlwr(p1);
                grp = group_find(p1);
                from = atoi(p2);
                if (grp<0) {
                   lprintf(bf,BFSZ-1,"Item not found {%s}\015\012",p1);
                   chan_write_s(chan,bf);
                   goto ret;                 
                }
                lprintf(bf,BFSZ-1,"Deleting item %d from group %s\015\012",from,group_name(grp));
                chan_write_s(chan,bf);
                item_delete(grp,from);
                db_clear_cache("killone");
                db_cancel_start();
        } else if (strcmp(cmd,"logchan")==0) {
                i = TRUE;
                strlwr(p2); if (strstr(p2,"off")!=NULL) i = FALSE;
                chan_setlog(atoi(p1),i);
        } else if (strcmp(cmd,"logfeed")==0) {
                i = TRUE;
                strlwr(p2); if (strstr(p2,"off")!=NULL) i = FALSE;
                feed_setlog(p1,i);
        } else if (strcmp(cmd,"rebuild")==0) {
                hist_rebuild();
        } else if (strcmp(cmd,"test_search")==0) {
		ncmd_dsearch_test(chan, p1,atoi(p2));	
        } else if (strcmp(cmd,"test_null")==0) {
		group_null_name(chan);
	} else if (strcmp(cmd,"resort_index")==0) {
                cmdq_add("resort_index");
        } else if (strcmp(cmd,"clear_cache")==0) {
                item_clearcache();
        } else if (strcmp(cmd,"crash_exit")==0) {
                dnews_ex("crash_exit"); 
        } else if (strcmp(cmd,"crash_now")==0) {
                emsg("Crash test\n");
                dmsg("fatal: Crash test\n");
                strcpy(NULL,"crash test");
        } else if (strcmp(cmd,"abort_now")==0) {
                {char *s=NULL; if (*s==0) s++;}
        } else if (strcmp(cmd,"expire")==0) {
                cmdq_add("expire");
                chan_write_s(chan,"    Expire queued to start\015\012");
        } else if (strcmp(cmd,"expire_groups")==0) {
		if (expire_ispile()) set_egroups(p1);
                lprintf(bf,BFSZ-1,"expire_groups %s",p1);
                cmdq_add(bf);
                chan_write_s(chan,"    Expire_groups queued to start\015\012");
        } else if (strcmp(cmd,"expire_xover")==0) {
                if (!suck_doxover()) {
                        chan_write_s(chan,"481 Already busy, sorry!\015\012");
                        goto ret;
                }
                chan_write_s(chan,"    Expire Xover queued to start\015\012");
        } else if (strcmp(cmd,"debug")==0) {
                strcpy(debug_str,p1);
        } else if (strcmp(cmd,"fileonly")==0) {
                item_fileonly(); /* Store in files only */ 
        } else if (strcmp(cmd,"speed_test")==0) {
                xspeed_test = !xspeed_test;
                if (xspeed_test) emsg("Speed test, junking articles\n");
                else emsg("Speed test finished\n");
                item_speed_test(xspeed_test);
        } else if (strcmp(cmd,"copy_group")==0) {
		if (strcmp(p1,p2)==0) {
	                chan_write_s(chan,"   cannot copy a group to itself\015\012");
		} else copy_group(chan,p1,p2,FALSE);
        } else if (strcmp(cmd,"copy_group_force")==0) {
		if (strcmp(p1,p2)==0) {
	                chan_write_s(chan,"   cannot copy a group to itself\015\012");
		} else copy_group(chan,p1,p2,TRUE);
        } else if (strcmp(cmd,"copy_item")==0) {
                copy_item(chan,p1,p3,atoi(p2));
        } else if (strcmp(cmd,"pile_move")==0) {
                db_pile_move(chan,p1,p2);
        } else if (strcmp(cmd,"copy_thread")==0) {
                copy_items(chan,p1,p2,"",get_rest2(in,5),FALSE);
        } else if (strcmp(cmd,"move_thread")==0) {
                copy_items(chan,p1,p2,"",get_rest2(in,5),TRUE);
        } else if (strcmp(cmd,"delete_thread")==0) {
                copy_items(chan,p1,"","",get_rest2(in,4),TRUE);
        } else if (strcmp(cmd,"copy_subject")==0) {
                copy_items(chan,p1,p2,get_rest2(in,5),"",FALSE);
        } else if (strcmp(cmd,"move_subject")==0) {
                copy_items(chan,p1,p2,get_rest2(in,5),"",TRUE);
        } else if (strcmp(cmd,"delete_subject")==0) {
                copy_items(chan,p1,"",get_rest2(in,4),"",TRUE);
        } else if (strcmp(cmd,"hist_corrupt")==0) {
                hist_corrupt();
        } else if (strcmp(cmd,"test_idle")==0) {
                nntp_measure_idle(TRUE);
         } else if (strcmp(cmd,"pgp_test")==0)  {
                lprintf(bf,BFSZ-1,"check dnews.log to see if it worked\015\012");
                xitem = str_new();
		if (strlen(p1)>0) str_read(xitem,p1);
		else str_read(xitem,file_work("pgp.tst"));
                if (pgp_verify(xitem,"")) {
			wmsg("SUCCESS: Test PGP message was verified, it would be actioned if it was a real post\n");
			chan_write_s(chan,"SUCCESS: Test PGP message was verified, it would be actioned if it was a real post\r\n");
		} else {
			wmsg("FAIL: Test PGP message was rejected for some reason\n");
			chan_write_s(chan,"FAIL: Test PGP message was rejected for some reason\r\n");
		}
                str_free(xitem);
        } else if (strcmp(cmd,"spam_age")==0)  {
                spam_crc_age();
                spam_from_age();
        } else if (strcmp(cmd,"hist_analyze")==0) {
                hist_analyze();
        } else if (strcmp(cmd,"test_strings3")==0) {
                test_strings_lots_n(atoi(p1),atoi(p2));
        } else if (strcmp(cmd,"test_strings2")==0) {
                emsg("test strings compress\n");
                xstring = str_new();
                str_setlen(xstring,70000);
                xstring = str_new();
                str_setlen(xstring,180000);
                xstring = str_new();
                str_setlen(xstring,1720000);
                p_compress_between();
                emsg("Done compress\n");
        } else if (strcmp(cmd,"test_strings")==0) {
                test_strings();
        } else if (strcmp(cmd,"test_user")==0) {
/*
#ifdef WIN32
                { int utoken;
                LogonUser("chrisp",NULL,"xxx",0,0,&utoken);
                }
#endif
*/
        } else if (strcmp(cmd,"test_idle_off")==0) {
                nntp_measure_idle(FALSE);
        } else if (strcmp(cmd,"loglvl")==0) {
                debug_setlvl(p1);
        } else if (strcmp(cmd,"write_crap")==0) {
		chan_write_s(atoi(p1),p2);
        } else if (strcmp(cmd,"thin_debug")==0) {
                thin_setdebug();
        } else if (strcmp(cmd,"cached")==0)  {
                if (strlen(p3)==0) strcpy(p3,"100");
                lprintf(bf,BFSZ-1,"Setting {%s} to cached %s days \015\012",p1,p2); chan_write_s(chan,bf);
                for (i=0; i<group_n(); i++) {
                  if (group_match(p1,group_namelc(i))) {
                        group_setlife(i,atoi(p2));
                        group_setcached(i,TRUE);
                        ndone++;
                  }
                }
                lprintf(bf,BFSZ-1,"%d groups cached \015\012",ndone); chan_write_s(chan,bf);
        } else if (strcmp(cmd,"read_check")==0)  {
                for (i=0; i<group_n(); i++) {
                  if (!item_read_check_group(i)) {
                    lprintf(bf,BFSZ-1,"CORRUPT ITEMS in %s\015\012",group_name(i)); chan_write_s(chan,bf);     
                  } 
                }
        } else if (strcmp(cmd,"too_old_ignore")==0)  {
                too_old_off();
/*        } else if (strcmp(cmd,"test_xover")==0)  {
                item_test_xover(chan);*/
        } else if (strcmp(cmd,"xover_test")==0)  {
		item_xover_test(chan,group_find(p1));
        } else if (strcmp(cmd,"backup_groups")==0)  {
                cmd_extract(chan,p1);
        } else if (strcmp(cmd,"test_read")==0)  {
                item_test_read(chan,p1);
        } else if (strcmp(cmd,"test_group")==0)  {
                item_test_read(chan,p1);
        } else if (strcmp(cmd,"xcached")==0)  {
                lprintf(bf,BFSZ-1,"Setting {%s} to xover cached \015\012",p1); chan_write_s(chan,bf);
                for (i=0; i<group_n(); i++) {
                  if (group_match(p1,group_namelc(i))) {
                        group_setxover(i,0);
                        ndone++;
                  }
                }
                lprintf(bf,BFSZ-1,"%d groups xover cached \015\012",ndone); chan_write_s(chan,bf);
        } else if (strcmp(cmd,"uncached")==0)  {
                lprintf(bf,BFSZ-1,"Setting {%s} to uncached \015\012",p1); chan_write_s(chan,bf);
                for (i=0; i<group_n(); i++) {
                  if (group_match(p1,group_namelc(i))) {
                        group_setlife(i,0);
                        group_setcached(i,FALSE);
                        ndone++;
                  }
                }
                lprintf(bf,BFSZ-1,"%d groups uncached \015\012",ndone); chan_write_s(chan,bf);
        } else if (strcmp(cmd,"xuncached")==0)  {
                lprintf(bf,BFSZ-1,"Setting {%s} to uncached \015\012",p1); chan_write_s(chan,bf);
                for (i=0; i<group_n(); i++) {
                  if (group_match(p1,group_namelc(i))) {
                        group_setxover(i,50);
                        ndone++;
                  }
                }
                lprintf(bf,BFSZ-1,"%d groups xuncached \015\012",ndone); chan_write_s(chan,bf);
        } else if (strcmp(cmd,"rmgroup")==0)  {
                strlwr(p1);
                lprintf(bf,BFSZ-1,"Setting {%s} to deleted state \015\012",p1); chan_write_s(chan,bf);
                for (i=0; i<group_n(); i++) {
                  if (group_match(p1,group_namelc(i))) {
                        wmsg("rmgroup %s \n",group_name(i)); ndone++;
                        lprintf(bf,BFSZ-1,"Group Marked for delete {%s} \015\012",group_name(i)); chan_write_s(chan,bf);
                        group_setlife(i,0);
                        group_setcached(i,FALSE);
                        group_delete(i);
                  }
                }
                lprintf(bf,BFSZ-1,"%d groups marked for delete \015\012",ndone); chan_write_s(chan,bf);
                if (ndone==0) {
                        chan_write_s(chan,"NO GROUPS MATCHED\r\n");
                }
                slave_flushsnd();
        } else if (strcmp(cmd,"upgrade")==0)  {
                chan_write_s(chan," ** WRONG COMMAND, use DOUPGRADE to upgrade the database\015\012");
                chan_write_s(chan," ** or use UPDATE to fetch new items\015\012");
        } else if (!do_cmd2(chan,cmd,p1)) goto next_bit22;
        goto end_bit;
next_bit22:
        if (strcmp(cmd,"stats")==0)  {
                chan_write_s(chan,"\015\012");
                chan_write(chan,stats_show());
                chan_write_s(chan,".\015\012");
        } else if (strcmp(cmd,"stats_in")==0)  {
		chan_purge();
                chan_write_s(chan,"\015\012");
                chan_write(chan,stats_show_in());
                chan_write_s(chan,".\015\012");
        } else if (strcmp(cmd,"out_clear")==0)  {
                feed_statsclear();
        } else if (strcmp(cmd,"in_clear")==0)  {
                stats_clear();
        } else if (strcmp(cmd,"stats_clear")==0)  {
                xstats_clear();
        } else if (strcmp(cmd,"stats_out")==0)  {
                chan_write_s(chan,"\015\012");
                chan_write(chan,feed_out());
                chan_write_s(chan,".\015\012");
        } else if (strcmp(cmd,"stats_groups")==0)  {
                chan_write_s(chan,"\015\012");
                chan_write(chan,stats_groups_read(p1,chan));
                chan_write_s(chan,".\015\012");
        } else if (strcmp(cmd,"stats_groups_clear")==0)  {
		stats_groups_clear();
                chan_write_s(chan,"stats_groups cleared\015\012");
        } else if (strcmp(cmd,"fixnames")==0)  {
                lprintf(bf,BFSZ-1,"Fixed active.names file, %d deleted records\015\012",group_fixnames());
                chan_write_s(chan,bf);
        } else if (strcmp(cmd,"test_files")==0)  {
                lprintf(bf,BFSZ-1,"Max open files on this system %d dbf=%d\015\012",lib_test_files(),dbf_nfree());
                chan_write_s(chan,bf);
        } else if (strcmp(cmd,"test_match")==0)  {
		if (group_match(p1,p2)) {
			lprintf(bf,BFSZ-1,"Groups matched OK. (%s) (%s)\r\n",p1,p2);
			chan_write_s(chan,bf);
		}
		else if (group_match_none()) {
			lprintf(bf,BFSZ-1,"@ match detected. (%s) (%s)\r\n",p1,p2);
			chan_write_s(chan,bf);
		}
        } else if (strcmp(cmd,"thin_trace")==0) {
                thin_set_trace(atoi(p1));
        } else if (strcmp(cmd,"test_sockets")==0)  {
                lprintf(bf,BFSZ-1,"Max open sockets on this system %d \015\012",nntp_nsockets());
                chan_write_s(chan,bf);
        } else if (strcmp(cmd,"test_sleep")==0)  {
                sleep(atoi(p1));
        } else if (strcmp(cmd,"group_read")==0)  {
                  imsg("group_read started \n");
                  for (i=0; i<20; i++) {
                          g_group_read(0);
                  }
                  imsg("group_read finished \n");
        } else if (strcmp(cmd,"test_idx")==0)  {
                db_test_idx(chan,atoi(p1),atoi(p2));
        } else if (strcmp(cmd,"test_gap")==0)  {
                ncmd_set_gap();
        } else if (strcmp(cmd,"test_nocem")==0)  {
                nocem_test(chan,p1);
        } else if (strcmp(cmd,"test_gup")==0)  {
                gup_send();
        } else if (strcmp(cmd,"test_mem")==0)  {
                
                cmd_test_malloc(chan,atoi(p1));
        } else if (strcmp(cmd,"stop_xactive")==0)  {
                xactive_disable = TRUE;
        } else if (strcmp(cmd,"start_xactive")==0)  {
                xactive_disable = FALSE;
        } else if (strcmp(cmd,"test_lastmb")==0)  {
                db_sort_start();
        } else if (strcmp(cmd,"free_gindex")==0)  {
                g_free_all("free_gindex_cmd");
        } else if (strcmp(cmd,"check_group")==0)  {
                db_check_group(p1);
        } else if (strcmp(cmd,"corrupt_mem")==0)  {
                {
                        char *s = mymalloc2(10,M_CMD);
                        for (i= -321; i<330; i+=2) s[i]=i;
                }
        } else if (strcmp(cmd,"test_stack")==0)  {
                test_stack();
        } else if (strcmp(cmd,"trace_expire")==0)  {
                expire_tracemid(p1);
        } else if (strcmp(cmd,"test_str")==0)  {
                lprintf(bf,BFSZ-1,"String test completed %d \015\012",lib_test_str());
                chan_write_s(chan,bf);
        } else if (strcmp(cmd,"test_history")==0)  {
#ifdef DNEWS2
                hist_stress();
#endif
        } else if (strcmp(cmd,"setused")==0)  {
                db_correct_used();
                lprintf(bf,BFSZ-1,"181 Use tellnews check_buckets if this value is still not right\015\012"); chan_write_s(chan,bf);
                lprintf(bf,BFSZ-1,"181 Setting diskuse to %d MB\015\012",diskuse_get()/100); chan_write_s(chan,bf);
        } else if (strcmp(cmd,"matchfeed")==0)  {
                for (i=0; i<group_n(); i++) {
                  if (!group_wanted(group_namelc(i))) {
                        wmsg("matchfeed rmgroup %s \n",group_name(i));
                        lprintf(bf,BFSZ-1,"Group Marked for delete {%s} \015\012",group_name(i)); chan_write_s(chan,bf);
                        group_setlife(i,0);
                        group_setcached(i,FALSE);
                        group_delete(i);
                  }
                }
        } else if (strcmp(cmd,"undelete")==0)  {
                strlwr(p1);
                lprintf(bf,BFSZ-1,"Setting {%s} to undeleted state \015\012",p1); chan_write_s(chan,bf);
                for (i=0; i<group_n(); i++) {
                  if (group_match(p1,group_namelc(i))) {
                        group_undelete(i);
                        ndone++;
                  }
                }
                lprintf(bf,BFSZ-1,"%d groups marked for undelete \015\012",ndone); chan_write_s(chan,bf);
                slave_flushsnd();
        } else if (strcmp(cmd,"killgroup")==0)  {
                strlwr(p1);
                lprintf(bf,BFSZ-1,"Setting {%s} to deleted state \015\012",p1); chan_write_s(chan,bf);
                for (i=0; i<group_n(); i++) {
                  if (group_match(p1,group_namelc(i))) {
                        group_setlife(i,0);
                        group_setcached(i,FALSE);
                        group_delete(i);
                        if (group_remove_now(i)){
                                lprintf(bf,BFSZ-1,"Group deleted {%s} \015\012",group_name(i)); chan_write_s(chan,bf);
                                imsg("Group actually deleted %s \n",group_name(i));
                        }
                        ndone++;
                  }
                }
                lprintf(bf,BFSZ-1,"%d groups killed, we recommend you shutdown and restart dnews now. \015\012",ndone); chan_write_s(chan,bf);
        } else if (strcmp(cmd,"purgegroups")==0)  {
                strlwr(p1);
                if (strcmp(p1,"")==0) strcpy(p1,"*");
                lprintf(bf,BFSZ-1,"Purging deleted groups matching {%s} \015\012",p1); chan_write_s(chan,bf);
                for (i=0; i<group_n(); i++) {
                
                  if (group_isdeleted(i)) if (group_match(p1,group_namelc(i))) {
                        group_setlife(i,0);
                        group_setcached(i,FALSE);
                        group_delete(i);
                        if (group_remove_now(i)){
                                lprintf(bf,BFSZ-1,"Group deleted {%s} \015\012",group_name(i)); chan_write_s(chan,bf);
                                imsg("Group actually deleted %s \n",group_name(i));
                        }
                        ndone++;
                  }
                }
                lprintf(bf,BFSZ-1,"%d groups killed, we recommend you shutdown and restart dnews now! \015\012",ndone); chan_write_s(chan,bf);
        } else if (strcmp(cmd,"resetall")==0)  {
                chan_write_s(chan," running check_index instead as it's safer\r\n");
                db_check_index();
        } else if (strcmp(cmd,"real_resetall")==0)  {
                for (i=0; i<group_n(); i++) {
                        /* group_setlife(i,10000);*/
                        group_setstart(i,group_getend(i)+1);
                        group_setfeedend(i,0,0);
                }
        } else if (strcmp(cmd,"setend")==0)  {
                lprintf(bf,BFSZ-1,"Set end for groups {%s} to at least %d \015\012",p1,atoi(p2)); chan_write_s(chan,bf);
                f = atoi(p2);
                for (i=0; i<group_n(); i++) 
                    if (group_match(p1,group_namelc(i))) {
                        n = group_getend(i);
                        if (n<f) group_setend(i,f);
                }
        } else if (strcmp(cmd,"pause")==0)  {
                cmd_pause(TRUE,get_rest(in,2));
                lprintf(bf,BFSZ-1,"Pausing incoming feeds, reason (%s) \015\012",get_reason());
                chan_write_s(chan,bf);
        } else if (strcmp(cmd,"pause_ihave")==0)  {
                cmd_pause_ihave(TRUE,get_rest(in,2));
                lprintf(bf,BFSZ-1,"Pausing incoming IHAVE feeds, reason (%s) \015\012",get_reason());
                chan_write_s(chan,bf);
        } else if (strcmp(cmd,"trim_range")==0)  {
                db_trim_range();
        } else if (strcmp(cmd,"unpause")==0)  {
                cmd_pause(FALSE,"");
                lprintf(bf,BFSZ-1,"Un Pausing incoming feeds\015\012");
                chan_write_s(chan,bf);
        } else if (strcmp(cmd,"unpause_ihave")==0)  {
                cmd_pause_ihave(FALSE,"");
                lprintf(bf,BFSZ-1,"Un Pausing incoming IHAVE feeds\015\012");
                chan_write_s(chan,bf);
        } else if (strcmp(cmd,"newgroup")==0)  {
                lprintf(bf,BFSZ-1,"New group {%s}, Modflag {%s}, Creator {%s}  Description {%s} \015\012",p1,p2,p3,get_rest(in,6));
                chan_write_s(chan,bf);
                g = group_find(p1);
                if (g>=0) if (!group_isdeleted(g)) {
                        chan_write_s(chan,"Modifying group that already exists\r\n");   
                }
                lprintf(bf,BFSZ-1,"%s",get_rest(in,6));
                if (strlen(p2)==0) strcpy(p2,"y");
                strlwr(p2);
                if (strcmp(p2,"m")!=0) strcpy(p2,"y");
                if (group_new(p1,p3,p2,bf)<0) {
                        chan_write_s(chan," Group creation failed, check ME feed in newsfeeds.conf and max_groups setting (examine dnews.log too)\r\n");
                }
        } else if (strcmp(cmd,"showdbn")==0)  {
                chan_write_s(chan,"\015\012");
                chan_write(chan,db_showdbn());
                chan_write_s(chan,".\015\012");
        } else if (strcmp(cmd,"showpiles")==0)  {
                db_show_pile(chan);
        } else if (strcmp(cmd,"showdisks")==0)  {
                chan_write_s(chan,"\015\012");
                chan_write(chan,db_showdisks());
                chan_write_s(chan,".\015\012");
        } else if (strcmp(cmd,"choose")==0)  {
                db_choose_next(); /* Force it to choose the next free bucket */
        } else if (strcmp(cmd,"status")==0)  {
                chan_write_s(chan,"\015\012");
                chan_write(chan,dnews_status());
                if (!config_true(C_ALLOW_WORLD)) {
                        check_world(chan);
                }
                chan_write_s(chan,".\015\012");
		str_write(dnews_status(),file_log("status.log"));
        } else if (strcmp(cmd,"showqueue")==0)  {
                chan_write_s(chan,"\015\012");
                for (i=0; i<suck_quelen(); i++) {
                        chan_write_s(chan,suck_showque(i));
                }
                chan_write_s(chan,".\015\012");
        } else if (strcmp(cmd,"showmem")==0)  {
                chan_write_s(chan,"\015\012");
                chan_write(chan,lib_showmem());
		str_write(lib_showmem(),file_log("showmem.log"));
                chan_write_s(chan,".\015\012");
        } else if (strcmp(cmd,"showblocks")==0)  {
                chan_write_s(chan,"\015\012");
		{	
			String *xstr;
			xstr = str_new();
			mem_blocks_show_str(xstr);
	                chan_write(chan,xstr);
	                chan_write_s(chan,".\015\012");
			str_free(xstr);
		}
        } else if (strcmp(cmd,"isbusy")==0)  {
                chan_write_s(chan,suck_isbusy());
        } else if (strcmp(cmd,"show_tomail")==0)  {
                chan_write_s(chan,"\015\012");
                tomail_show(chan);
        } else if (strcmp(cmd,"dns_show")==0)  {
                chan_write_s(chan,"\015\012");
                chan_write(chan,nntp_show_dns());
                chan_write_s(chan,".\015\012");
        } else if (strcmp(cmd,"dns_clear")==0)  {
                chan_write_s(chan,"All cached DNS entries removed.\015\012");
                dns_clear();
        } else if (strcmp(cmd,"speed")==0)  {
                chan_write_s(chan,item_speed());
        } else if (strcmp(cmd,"speed_reset")==0)  {
                chan_write_s(chan,"Speed statistics zeroed\015\012");
                item_speed_reset();
        } else if (strcmp(cmd,"log")==0)  {
                cmd_emsg_tail(chan,p1); /* Show last n lines of log file */
        } else if (strcmp(cmd,"showfile")==0)  {
                cmd_showfile(chan,p1); /* Show config file */
        } else { goto next_bit; }
        goto end_bit;
next_bit:;        
        if (strcmp(cmd,"rotate")==0)  {
                emsg_rotate();
                chan_write_s(chan,"Log files rotated, log,in,out.\015\012");
        } else if (strcmp(cmd,"user_hide")==0) {
		lprintf(bf,BFSZ-1,"Hidden string is (%s)\r\n",hide_user(p1));
		chan_write_s(chan,bf);
        } else if (strcmp(cmd,"user_unhide")==0) {
		lprintf(bf,BFSZ-1,"UnHidden string is (%s)\r\n",unhide_user(p1));
		chan_write_s(chan,bf);
        } else if (strcmp(cmd,"report")==0)  {
                dnews_manager_report("Manager report");
        } else if (strcmp(cmd,"rotate_rej")==0)  {
		rej_rotate();
        } else if (strcmp(cmd,"reque")==0)  {
                feed_doit();
        } else if (strcmp(cmd,"refeed")==0)  {
                i = feed_find(p1);
                if (i<0) {
                        lprintf(bf,BFSZ-1,"500 Feed (%s) not found\015\012",p1);
                        chan_write_s(chan,bf);
                } else {
                        suck_refeed(i);
                        chan_write_s(chan,"Refeed has been queued, watch progress with status.\015\012");
                }
      } else if ((strcmp(cmd,"register")==0)||(strcmp(cmd,"xregister")==0))  {
                dnews_getkey(bf);
                if (strlen(bf)!=0) {
                        lprintf(bf2,BFSZ-1,"483 %s\015\012",bf); chan_write_s(chan,bf2);
                        lprintf(bf2,BFSZ-1,"   Sorry 'register' command is obsolete use http://netwinsite.com/activate.htm to register\015\012",bf); chan_write_s(chan,bf2);
                }
      } else if ((strcmp(cmd,"deactivate")==0))  {
		strcat(p1,"_deactivate");
		goto xx;
      } else if ((strcmp(cmd,"key")==0)||(strcmp(cmd,"activate")==0))  {
xx:		strcpy(key_info,"");
                if (nguess>30) {
                        lprintf(bf,BFSZ-1,"582 Sorry, too many guesses {%s} for pc {%s} - restart dnews\015\012",p1,host_details());
                        chan_write_s(chan,bf);
                        goto ret;
                }
                nguess++;
                strlwr(p1);
                if (strlen(p1)<2) {
                        lprintf(bf,BFSZ-1,"582 Please enter a valid key e.g. n12323 not {%s} for pc {%s} \015\012",p1,host_details());
                        chan_write_s(chan,bf);
                        goto ret;
                }
#ifdef KEYLIB
                if (dnews_setkey(keylib_two(p1,p2))) {
#else
                if (dnews_setkey(p1)) {
#endif
                        chan_write_s(chan,"282 License key loaded SUCCESSFULLY, Thank you\015\012");
                } else {
                        lprintf(bf,BFSZ-1,"582 License key failed {%s} {%s}, sorry\015\012",p1,host_details());
                        chan_write_s(chan,bf);
                        lprintf(bf,BFSZ-1,"582 Reason (%s)\015\012",key_reason());
                        chan_write_s(chan,bf);
#ifdef KEYLIB
                        lprintf(bf,BFSZ-1,"582 - See http://netwinsite.com/activate.htm for activation instructions\015\012");
                        chan_write_s(chan,bf);
#else
                        lprintf(bf,BFSZ-1,"582 - Email sales@netwinsite.com for help - please include the above info in full\015\012");
                        chan_write_s(chan,bf);
#endif
                }
                dnews_getkey(bf);
                if (strlen(bf)!=0) {
                        lprintf(bf2,BFSZ-1,"483 %s\015\012",bf); chan_write_s(chan,bf2);
                }
	} else if (strcmp(cmd,"hist_test_consistency")==0)  {
		hist_test_consistency(chan);
	} else if (strcmp(cmd,"matchmod")==0)  {
		n=group_matchmod(chan,p1);
		if(n>=0) {
			lprintf(bf,BFSZ-1,"Fixed moderation settings for %d groups\r\n",n);
			chan_write_s(chan,bf);
		}
        } else if (strcmp(cmd,"help")==0)  {
                chan_write_s(chan,"100 Legal manager commands\015\012");
                chan_write_s(chan,"activate Nnn email@address      Enter your registration key\015\012");
                chan_write_s(chan,"cached wild days                Set a group to cached  \015\012");
                chan_write_s(chan,"check_index                     Correct group ranges based on index\015\012");
                chan_write_s(chan,"checkgroups                     Process checkgroups file\015\012");
                chan_write_s(chan,"clear_cache                     Clear some of the internal caches\015\012");
                chan_write_s(chan,"debug chan_read:expire:lib:     Set specific debugging output\015\012");
                chan_write_s(chan,"delete_items group.name from to Deletes a range of items in a group\015\012");
                chan_write_s(chan,"disconnect n                    Disconnect the specified channel\015\012");
                chan_write_s(chan,"diskuse [groupwild]             Show approximate disk use of specified groups\015\012");
                chan_write_s(chan,"dns_clear                       Clear cache of ip names/numbers\015\012");
                chan_write_s(chan,"dns_show                        Show ip names/numbers in cache\015\012");
                chan_write_s(chan,"exit                            Closedown the daemon cleanly\015\012");
                chan_write_s(chan,"expire                          Run the expire process\015\012");
                chan_write_s(chan,"expire_groups                   Delete all contents of group(s) (runs an expire)\015\012");
                chan_write_s(chan,"fixnames                        Check the active.names file and repair\015\012");
                chan_write_s(chan,"fixtimes n                      Reset the active.times file to n days ago\015\012");
                chan_write_s(chan,"getgroups [n]                   Get any new groups from that suck feed\015\012");
                chan_write_s(chan,"getnames                        Get the active.names from our feeder \015\012");
                chan_write_s(chan,"getold wildgrp days             Get older news from the feeder\015\012");
                chan_write_s(chan,"group_age                       Age all groups one day\015\012");
                chan_write_s(chan,"group_merge                     Merge active.add into active.dat\015\012");
                chan_write_s(chan,"group_nodup                     Remove duplicate groups in the active file\015\012");
                chan_write_s(chan,"killgroup wild.group.name       Actually delete group from active.dat if empty \015\012");
                chan_write_s(chan,"killone grpname num             Delete a specific item\015\012");
                chan_write_s(chan,"killitem grpname num            Delete a specific item and cross posts\015\012");
                chan_write_s(chan,"killitem \"<message_id>\"         Delete by message id, quotes essential\015\012");
                chan_write_s(chan,"log                             Display last 50 lines of log file\015\012");
                chan_write_s(chan,"loglvl info|debug|flush         Set debug level on the fly\015\012");
                chan_write_s(chan,"logchan n                       Create file chan_n.log for channel [n]\015\012");
                chan_write_s(chan,"matchfeed                       Delete groups that don't match ME feed\015\012");
                chan_write_s(chan,"matchgroups                     Add and remove groups to match the place we suck from\015\012");
                chan_write_s(chan,"matchfile x.dat                 Add and remove groups to match the contents of x.dat\015\012");
                chan_write_s(chan,"matchmod x.dat                  Set moderated flag to match example file, dont add/remove groups\015\012");
                chan_write_s(chan,"newgroup name y|m creator desc  Add a new group\015\012");
                chan_write_s(chan,"purgegroups                     Remove groups marked as deleted\015\012");
                chan_write_s(chan,"rebuild_index                   Delete db index and rebuild from item files, takes hours\015\012");
                chan_write_s(chan,"rebuild_hist                    Scan db for items not in the history file\015\012");
                chan_write_s(chan,"rebuild_hcache                  Rebuild header cache index\015\012");
                chan_write_s(chan,"check_index                     Scan index and correct group ranges\015\012");
                chan_write_s(chan,"check_buckets                   Scan buckets and find any missing ones\015\012");
                chan_write_s(chan,"refeed                          Send articles again, to create fts index\015\012");
                chan_write_s(chan,"register                        Create register.txt \015\012");
                chan_write_s(chan,"reload                          Reload news.conf ...\015\012");
                chan_write_s(chan,"reload_fts                      Get a new fts index from disk\015\012");
                chan_write_s(chan,"reject                          Reject a specific item from Ihave feed\015\012");
                chan_write_s(chan,"remove_lost                     Removes items not in history (medium speed)\015\012");
                chan_write_s(chan,"report                          EMail status to manager\015\012");
                chan_write_s(chan,"reque                           Requeue builtin feeds \015\012");
                chan_write_s(chan,"resetall                        (ONLY FOR NEW SYSTEMS) Clears ALL item numbers\015\012");
                chan_write_s(chan,"rmgroup wild.group.name         Mark group as deleted\015\012");
                chan_write_s(chan,"rotate                          Rotate the .log files\015\012");
                chan_write_s(chan,"setused                         Set Megabytes used \015\012");
                chan_write_s(chan,"showcached [rec.*]              Show list of cached news groups \015\012");
                chan_write_s(chan,"showgroups [rec.*]              Show info about some groups\015\012");
                chan_write_s(chan,"showgroup rec.arts.drwho        Show items in a group \015\012");
                chan_write_s(chan,"showmem                         Show mem use in detail\015\012");
                chan_write_s(chan,"showqueue                       Show the contents of the update queue\015\012");
                chan_write_s(chan,"showusers                       Show users who have been connected and stats\015\012");
                chan_write_s(chan,"speed                           Show how fast articles are arriving\015\012");
                chan_write_s(chan,"speed_reset                     Reset the counters used above\015\012");
                chan_write_s(chan,"speed_test                      THROWS AWAY ARTICLES - TESTING ONLY\015\012");
                chan_write_s(chan,"stats_groups                    Show statistics on groups since startup\015\012");
                chan_write_s(chan,"stats_in                        Show statistics on incoming news since last expire\015\012");
                chan_write_s(chan,"stats_out                       Show statistics on outgoing feeds\015\012");
                chan_write_s(chan,"status                          Show what server is doing \015\012");
                chan_write_s(chan,"status_hcache                   Show status of body caching\015\012");
                chan_write_s(chan,"test_mem                        Check how much mem dnews can allocate\015\012");
                chan_write_s(chan,"test_files                      Check number of file handles available\015\012");
                chan_write_s(chan,"test_sleep n                    Lock up Dnews for n seconds\015\012");
                chan_write_s(chan,"test_sockets                    Check how many sockets dnews can open\015\012");
                chan_write_s(chan,"trimgroups n                    Delete groups not on upstream site\015\012");
                chan_write_s(chan,"uncached wild                   Set a group to uncached  \015\012");
                chan_write_s(chan,"undelete wild.group             Mark deleted groups as normal again\015\012");
                chan_write_s(chan,"update                          Start a suck update\015\012");
                chan_write_s(chan,"update2                         Start a suck update for suck2\015\012");
                chan_write_s(chan,"update3                         Start a suck update for suck3\015\012");
                chan_write_s(chan,"updateone wild [n]              Get matching cached groups from suck n\015\012");
                chan_write_s(chan,"update_clear                    Clear current update queue\015\012");
                chan_write_s(chan,"Usage: tellnews -s n            To send command to slave 'n'015\012");
                chan_write_s(chan,".\015\012");
        } else {
                chan_write_s(chan,"181 Unknown Manager command, try HELP\015\012");
                goto ret;
        }
end_bit:
ret:
        if (strlen(key_info)>0) chan_write_s(chan,key_info);
        chan_write_s(chan,"281 Completed\015\012");
        return TRUE;
}
int cmd_findart(char *p1, char *p2, int *grp, int *num)
{
        if (*p1 == '<') {
                if (!hist_find(p1,grp,num)) {
                        return FALSE;
                }
        } else {
                strlwr(p1);
                *grp = group_find(p1);
                *num = atoi(p2);
        }
        return TRUE;    
}
int cmd_iteminfo(int chan, char *p1, char *p2)
{
        String *xitem;
        char *s;
        int grp,num;
        char bf[BFSZ];
        if (!cmd_findart(p1,p2,&grp,&num)) {
                lprintf(bf,BFSZ-1,"Item not found, group or mid=(%s) num=(%s) \015\012",p1,p2);
                chan_write_s(chan,bf);
                return TRUE;
        }
        lprintf(bf,BFSZ-1,"Found item %s %d \015\012",group_name(grp),num);
        chan_write_s(chan,bf);
        show_mrec_s(bf,grp,num);
        chan_write_s(chan,bf);
        xitem = item_head(grp,num);
        if (xitem==NULL) {
                chan_write_s(chan,"Header could not be Retrieved\r\n");
        } else {
                chan_write_s(chan,"Retrieved header successfully\r\n");
        }
        xitem = item_body(grp,num);
        if (xitem==NULL) {
                chan_write_s(chan,"Body could not be Retrieved\r\n");
        } else {
                chan_write_s(chan,"Retrieved body successfully\r\n");
        }
        s = hist_exists(p1);
        if (s!=NULL) {
                chan_write_s(chan,s);
        }
        return TRUE;
}
String *copy_cancel(String *item, char *newmid);
void make_cancel(int grp, int item)
{
        String *xitem;
        String *newhead;
        char newmid[BFSZ];
        xitem = item_head(grp,item);
        ncpy(newmid,get_ahead(xitem,"Message-ID:"),BFSZ-1);

        newhead = copy_cancel(xitem,newmid);
        if (newhead==NULL) {emsg("copy_cancel failed\n");return;}
        str_addsn(newhead,"\r\n");
        str_addsn(newhead,"Cancelling message on private group\r\n");
        feed_setpost(TRUE);
        if (item_suck(newhead)) {
                imsg("Cancel successfully created\n");
        } else {
                imsg("Cancel failed\n");
        }                       
        feed_setpost(FALSE);
}
String *copy_cancel(String *item, char *newmid)
{
        static String *newhead;
        char *e;
        char *z,*z2;
        char mid[BFSZ];
        char bf[BFSZ];
        char *s = str_s(item);
        int remove=FALSE;
        int h=lib_gmtime();

        str_lock(item);
        if (newhead==NULL) newhead= str_new();
        strcpy(mid,"nomidfound>");
        str_clearbig(newhead);
        for (;s!=NULL && *s!=0;) {
                if (strncmp(s,EOL,strlen(EOL))==0) break;
                if ((*s!=' ') && (*s!='\t')) remove = FALSE;
                e = strstr(s+1,EOL); if (e==NULL) break;

                if (strncmpnc(s,"Path:",5)==0) remove = TRUE;
                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); str_addsn(newhead,bf);
        lprintf(bf,BFSZ-1,"Control: cancel %s\r\n",newmid); str_addsn(newhead,bf);
        str_unlock(item);

        return newhead;
}

int do_cmd2(int chan, char *cmd, char *p1)
{
        int i;
        int x;
        char bf[BFSZ];
        if (strcmp(cmd,"doupgrade")==0)  {
                suck_upgrade();
        } else if (strcmp(cmd,"group_age")==0)  {
                group_age();
        } else if (strcmp(cmd,"repair_history")==0)  {
                hist_delete(); /* Start with a clean history file */
                suck_addlost();
        } else if (strcmp(cmd,"rebuild_hist")==0)  {
                suck_addlost();
        } else if (strcmp(cmd,"rebuild_ahist")==0)  {
                db_rebuild_ahist(atoi(p1));
        } else if (strcmp(cmd,"rebuild_control")==0)  {
                db_fix_control();
                cmd_pause(TRUE,"Rebuilding index");
                db_set_rebuild(TRUE); /* Tell it to delete the current index */
                suck_addlost();
        } else if (strcmp(cmd,"pull_clear")==0) {
		pull_clear();
                chan_write_s(chan,"Cleared update queue\015\012");
        } else if (strcmp(cmd,"pull_show")==0) {
              pull_show(chan,p1);
        } else if (strcmp(cmd,"rebuild_from")==0)  {
		db_set_rebuild_from(atoi(p1));
                cmdq_add("rebuild_index");
                cmdq_add("resort_index"); /*Always do this as it improves performance */ 
                chan_write_s(chan,"    Rebuild_index queued to start\015\012");
        } else if (strcmp(cmd,"rebuild_index")==0)  {
                cmdq_add("rebuild_index");
                cmdq_add("resort_index"); /*Always do this as it improves performance */ 
                chan_write_s(chan,"    Rebuild_index queued to start\015\012");
        } else if (strcmp(cmd,"rebuild_index_fast")==0)  {
                cmd_pause(TRUE,"Rebuilding index faster");
                db_set_rebuild_fast(TRUE); /* Tell it to delete the current index */
                suck_addlost();
        } else if (strcmp(cmd,"empty_count")==0)  {
                empty_count(chan,atoi(p1));
        } else if (strcmp(cmd,"empty_hide")==0)  {
                empty_hide(chan,atoi(p1));
        } else if (strcmp(cmd,"empty_unhide")==0)  {
                empty_unhide(chan,p1);
        } else if (strcmp(cmd,"remove_lost")==0)  {
                cmd_pause(TRUE,"Removing lost items");
                db_set_check(FALSE);
                db_set_addlost(TRUE);
                suck_addlost();
        } else if (strcmp(cmd,"getnames")==0) {
                if (!suck_update_names(atoi(p1))) {
                        chan_write_s(chan,"481 Already busy, sorry!\015\012");
                }
        } else if (strcmp(cmd,"extend")==0) {
                key_setextend();
                chan_write_s(chan,"   DNEWS will now work for the next 4 days, this should give you time to register your server\r\n");
                chan_write_s(chan,"   This command will not work again, sorry\r\n");
        } else if (strcmp(cmd,"test_pass_fail_add")==0) {
		for (i=0; i<10000; i++) {
			if (i%3==2) lprintf(bf,BFSZ-1,"%d.%d.%d",i,i,i);
			pass_fail_add(bf);
		}
        } else if (strcmp(cmd,"index_scan")==0)  {
                cmd_pause(TRUE,"Removing lost items");
                db_set_check(TRUE);
                db_set_addlost(TRUE);
                suck_addlost();
        } else if (strcmp(cmd,"rebuild_numbers")==0)  {
                cmd_pause(TRUE,"Renumbering entire spool");
                db_set_renumber(TRUE); 
                        /* Deletes the index, and the history file */
                        /* Can renumber groups at the same time ? */
                        /* Could rename groups if required */
                suck_addlost();
        } else if (strcmp(cmd,"rebuild_epoch")==0)  {
                cmd_pause(TRUE,"Renumbering entire spool with item numbers restarting at 2");
                for (i=0; i<group_n(); i++) {
                        /* group_setlife(i,10000);*/
                        group_setstart(i,3);
                        group_setend(i,2);
                }

                db_set_renumber(TRUE); 
                        /* Deletes the index, and the history file */
                        /* Can renumber groups at the same time ? */
                        /* Could rename groups if required */
                suck_addlost();
        } else if (strcmp(cmd,"rebuild_higher")==0)  {
		int fix=atoi(p1);
		if (fix==0) fix = 20000000;
                cmd_pause(TRUE,"Renumbering entire spool with item numbers restarting at");
                for (i=0; i<group_n(); i++) {
                        /* group_setlife(i,10000);*/
                        group_setstart(i,fix+1);
                        group_setend(i,fix);
                }

                db_set_renumber(TRUE); 
                        /* Deletes the index, and the history file */
                        /* Can renumber groups at the same time ? */
                        /* Could rename groups if required */
                suck_addlost();
        } else if (strcmp(cmd,"rebuild_gids")==0)  {
                cmd_pause(TRUE,"Renumbering GID's for entire spool");
                db_set_regid(TRUE); 
                suck_addlost();
        } else if (strcmp(cmd,"rebuild_piles")==0)  {
                cmd_pause(TRUE,"Rebuild piles for entire spool");
                db_set_regid(TRUE); 
                suck_addlost();
                chan_write_s(chan,"    Rebuild_piles queued to start\015\012");
        } else if (strcmp(cmd,"rebuild_rules")==0)  {
                cmd_pause(TRUE,"Rebuild piles for entire spool AND reapply rules");
		db_set_rebuild_rules();
                db_set_regid(TRUE); 
                suck_addlost();
        } else if (strcmp(cmd,"rerun_rules")==0)  {
                db_rerun_rules_start(atoi(p1));
        } else if (strcmp(cmd,"addlost")==0)  {
                chan_write_s(chan," ** Command replaced with \015\012");
                chan_write_s(chan," check_index     Check consistency of index, fix group ranges etc. (fast - 30 sec)\015\012");
                chan_write_s(chan," check_buckets   Find any lost bucket files, correct disk usage (fast - 30 sec)\015\012");
                chan_write_s(chan," rebuild_hist    Add any missing items to history database (slow - 1 hours)\015\012");
                chan_write_s(chan," rebuild_index   Deletes index and rebuilds from bucket files (Destructive! very slow - 8 hours )\015\012");
        } else if (strcmp(cmd,"reindex")==0)  {
                suck_reindex();
        } else if (strcmp(cmd,"upgrade_check")==0)  {
                scan_missing = TRUE;
                convert_dnewsfmt();
                suck_start_convert(p1);
        } else if (strcmp(cmd,"upgrade_scan")==0)  {
                scan_missing = FALSE;
                convert_dnewsfmt();
                suck_start_convert(p1);
        } else if (strcmp(cmd,"spool_convert")==0)  {
                suck_set_import(FALSE);
                suck_start_convert(p1);
        } else if (strcmp(cmd,"spool_import")==0)  {
                suck_set_import(TRUE);
                suck_start_convert(p1);
        } else if (strcmp(cmd,"ms_import")==0)  {
                suck_set_import(TRUE);
                convert_msfmt();
                suck_start_convert(p1);
        } else if (strcmp(cmd,"fixrange")==0)  {
                db_check_index();
        } else if (strcmp(cmd,"check_index")==0)  {
                db_check_index();
        } else if (strcmp(cmd,"check_buckets")==0)  {
                db_check_buckets();
        } else if (strcmp(cmd,"check_bucket_ages")==0)  {
                db_check_bucket_ages();
        } else if (strcmp(cmd,"group_merge")==0)  {
                group_merge(p1);
        } else if (strcmp(cmd,"groupmerge")==0)  {
                group_merge(p1);
        } else if (strcmp(cmd,"groups_merge")==0)  {
                group_merge(p1);
        } else if (strcmp(cmd,"group_nodup")==0)  {
                i = group_nodup();
                lprintf(bf,BFSZ-1,"Deleted %d groups, you must stop and start dnews\015\012",i);
                if (i==0) strcpy(bf,"No duplicate groups found \015\012");
                chan_write_s(chan,bf);
        } else if (strcmp(cmd,"fixtimes")==0)  {
                group_fixtimes(atoi(p1)*60*60*24);
        } else if (strcmp(cmd,"fixmod")==0)  {
                x = atoi(p1);
                if (x>100) x = 100;
                for (i=0; i<group_n(); i++) {
                        if (group_isdeleted(i)) continue;
                        if (group_getmrej(i)>x) {
                                lprintf(bf,BFSZ-1," Un moderating group %s which has had at least %d unapproved posts\r\n",group_name(i),group_getmrej(i));
                                chan_write_s(chan,bf);
                                group_setmod(i,"y");
                        }
                }
        } else if (strcmp(cmd,"showmod")==0)  {
                x = atoi(p1);
                if (x>100) x = 100;
                for (i=0; i<group_n(); i++) {
                        if (group_isdeleted(i)) continue;
                        if (group_getmrej(i)>x) {
                                lprintf(bf,BFSZ-1," %s has had at least %d unapproved posts\r\n",group_name(i),group_getmrej(i));
                                chan_write_s(chan,bf);
                        }
                }
                chan_write_s(chan,"Use tellnews fixmod to set the above groups to non moderated \r\n");
                chan_write_s(chan,"or check the web manual page updates.htm for other ways to correct your active file\r\n");
        } else if (strcmp(cmd,"hist_fast")==0)  {
                hist_setfast();
        } else if (strcmp(cmd,"hist_test")==0)  {
                hist_set_test(TRUE);
        } else return FALSE;
        return TRUE;
}


void test_stack(void)


{
        static tot;
        char bf[10000];
        strcpy(bf,"test message");
        tot += 10000;
        emsg("Total allocated on stack %d\n",tot);
        test_stack();
}

int test_strings_lots(void);
int test_strings(void)
{
        int i;
        for (i=0;i<1000;i++) {
                emsg("Test strings\n");
                test_strings_lots();
                if ((rand()%10) > 7) {
                        emsg("Flush");
                        p_flush();
                }
        }
        return TRUE;
}       
int test_strings_lots(void)
{
        static int done;
        static String *x[500];
        int i;
        int n;
        char *s;
        if (done) {
                /* check remaining strings ok */
                for (i=1,n=0;( i<200) && (n<40); i++) {
                        if (x[i]==NULL) continue;
                        if (rand()>RAND_MAX/2) continue;
                        n++;
                        s = str_s(x[i]);
                        str_free(x[i]);
                        x[i] = NULL;
                }
                emsg("Freed %d strings\n",n);
                if (n==0) goto add_more;
                return 0;
        } 
        srand(1);
        done = TRUE;
add_more:
        for (i=0; i<200; i++) {
                if (x[i]!=NULL) continue;
                x[i] = str_new();
                str_setlen(x[i],rand() % 50000);
        }
        return TRUE;    

}
int test_strings_lots_n(int n, int n2)
{
        static int done;
        static String *x[90000];
        int i;
        for (i=0; i<n; i++) {
                x[i] = str_new();
                str_setlen(x[i],rand() % n2);
        }
        for (i=0; i<n; i++) {
                if (rand()%100==3) {
                        str_free(x[i]);
                        x[i] = NULL;
                }
        }
        emsg("Start pflush \n");
        p_flush();
        emsg("Finished pflush \n");
        for (i=0; i<n; i++) {
                if (x[i]!=NULL) {
                        str_free(x[i]);
                        x[i] = NULL;
                }
        }

        return TRUE;    

}

/*-------- slave routines ------------------------------*/
static int slave_i;
static int slave_chan = -1;
static int slave_cmd[MAX_CHAN];
static int slave_xtimeout[MAX_CHAN];
static char *slave_msgid[MAX_CHAN];
static int slave_i_copy;
static int xismaster;
void slave_set(int n)
{
        slave_i = n;
	slave_i_copy = slave_i;
        if (n==0) xismaster = TRUE;
	if (n>0) nntp_set_isslave();
}

int is_slave(void)
{
	if (slave_i != slave_i_copy) {
		emsg("PANIC, SLAVE_I CHANGED %d to %d\n",slave_i,slave_i_copy);
		dnews_ex("slave_i changed");
	}
        if (slave_i>0) return TRUE;
        return FALSE;
}
int is_reader(void)
{
        if (atoi(config_get(C_READONLY_ID))>0) return TRUE;
        return FALSE;
}
int is_readmaster(void)
{
        if (!is_reader()) return FALSE;
        if (is_slave()) return FALSE;
        if (atoi(config_get(C_READONLY_N))>0) return FALSE;
        return TRUE;
}
int is_readonly(void)
{
        if (is_slave()) return TRUE;
        if (atoi(config_get(C_READONLY_ID))>0) return TRUE;
        return FALSE;
}
void readonly_xhit(int x)
{
        FILE *f;
        f = fopen(file_work("xhit.dat"),"a");
        if (f==NULL) return;
        fprintf(f,"%s\n",group_name(x));
        fclosep(f);
}
void readonly_setcached(int x)
{
        FILE *f;
        f = fopen(file_work("setcached.dat"),"a");
        if (f==NULL) return;
        fprintf(f,"%s\n",group_name(x));
        fclosep(f);
}
void readonly_readxhit(void)
{
        FILE *f;
        char *s;
        int g;
        char bf[BFSZ];
        if (atoi(config_get(C_READONLY_N))==0) return;
        f = fopen(file_work("xhit.dat"),"r");
        if (f==NULL) return;
        for (;!feof(f);) {
                if (fgets(bf,BFSZ-10,f)==NULL) break;
                s = strtok(bf," \r\n");
                if (s!=NULL) {
                        g = group_find(s);
			imsg("xhit: read xhit.dat and was told to xhit (%s) %d\n",s,g);
                        group_hitxover(g);
                }
        }
        fclosep(f);
        remove(file_work("xhit.dat"));
}
void readonly_readsetcached(void)
{
        FILE *f;
        char bf[BFSZ];
        char *s;
        int g;
        if (atoi(config_get(C_READONLY_N))==0) return;
        f = fopen(file_work("setcached.dat"),"r");
        if (f==NULL) return;
        for (;!feof(f);) {
                if (fgets(bf,BFSZ-10,f)==NULL) break;
                s = strtok(bf," \r\n");
                if (s!=NULL) {
                        g = group_find(s);
                        group_setcached_hit(g);
                }
        }
        fclosep(f);
        remove(file_work("setcached.dat"));
}
void readonly_check(void)
{
        int i;
        FILE *f;
        char bf[BFSZ],fname[BFSZ];

        if (atoi(config_get(C_READONLY_ID))==0) return; /* No need if we are the master */
	lprintf(bf,BFSZ-1,"pause%d.dat",atoi(config_get(C_READONLY_ID)));
        strcpy(fname,file_work(bf));
        f = fopen(fname,"r");
        if (f==NULL) return;
        fclosep(f);
        emsg("Master DNEWS asked readers to pause for index switching\n");
        db_flush2();
        db_clear_cache("readonly_check");
        hist_close();
        slave_flush(TRUE);
        emsg_close();

        for (i=0; i<1000; i++) {
                f = fopen(fname,"r");
                if (f==NULL) break;
                fclosep(f);
                lib_sleep(1);
                imsgf("Still waiting for master to let us go\n");
        }
        emsg("Master DNEWS said we can start again, yippee\n");
        slave_flush(FALSE);
        emsg("Reading new history file, may take 2 minutes\n");
        hist_reload();
        emsg("Reading new history file completed\n");
}
#include <errno.h>
int readonly_post(int chan, String *xitem)
{
        char bf[BFSZ];
        char bf2[BFSZ];
        FILE *f;
        static int cnt;
        char estring[BFSZ];
        int sent;
        lprintf(bf,BFSZ-1,"post%d_%d_%d.rdr",slave_i,atoi(config_get(C_READONLY_ID)),cnt++);
#ifdef WIN32
        f = fopen(file_work(bf),"wb");
#else
        f = fopen(file_work(bf),"w");
#endif
        if (f==NULL) {
                strcpy(estring,strerror(errno));
                filemsg("FATAL Write to %s failed, cannot post messages\n",bf);
failed:         
                lprintf(bf2,BFSZ-1,"441 posting failed - could not write to master dnews processes disk, report to news admin (%s)\015\012",estring);
                chan_write_s(chan,bf2);
                return FALSE;           
        }
        sent = ((int) fwrite(str_s(xitem),1,str_len(xitem),f));
        if (sent!=str_len(xitem)) {
                strcpy(estring,strerror(errno));
                filemsg("FATAL Writing to %s failed wrote %d of %d bytes, cannot post messages\n",bf,sent,str_len(xitem));
                fclosep(f);
                goto failed;
        }
        fprintf(f,".\r\n");
        fclosep(f);
        chan_write_s(chan,"240 article posted ok, wait 3 minutes for it to appear\015\012");
        return TRUE;
}
static int readonly_n;
static int rpaused;
void readonly_pause(void)
{       
        FILE *f;
        char bf[BFSZ];
        int i;
        readonly_n = atoi(config_get(C_READONLY_N));
        if (readonly_n==0) return;
        for (i=0; i<=readonly_n; i++) {
                rpaused = TRUE;
                lprintf(bf,BFSZ-1,"pause%d.dat",i);
                f = myfopen(file_work(bf),"w");
                if (f==NULL) {filemsg("Could not create pause file\n"); break;}
                fprintf(f,"%d",lib_gmtime());
                fclosep(f);
        }
        imsg("Waiting 5 seconds for readonly servers to pause\n");
        lib_sleep(5);
}
void readonly_unpause(void)
{
        char bf[BFSZ];
        int i;
        int j;
        if (readonly_n>0) if (rpaused) imsg("lib: Unpausing readonly servers\n");
        else return;
        rpaused = FALSE;
        for (i=0; i<=readonly_n; i++) {
                lprintf(bf,BFSZ-1,"pause%d.dat",i);
                for (j=0; j<100; j++) {
                        if (remove(file_work(bf))==0) break;
                }
        }
}
int slave_n(void)
{
	if (slave_i != slave_i_copy) {
		emsg("PANIC, SLAVE_I CHANGED %d to %d\n",slave_i,slave_i_copy);
		dnews_ex("slave_i changed");
	}

        return slave_i;
}

int expire_quanta(void)
{
        static int done;
        static int xdelay;
        if (done) return xdelay;
        done = TRUE;
        xdelay = atoi(config_get(C_EXPIRE_QUANTA));
        if (xdelay==0) {
                xdelay = 1;
                if (atoi(config_get(C_DISKSPACE))>2000) {
                        xdelay = 2;
                }
                if (atoi(config_get(C_SLAVE_N))>0) {
                        xdelay = 4;
                }
                if (atoi(config_get(C_DISKSPACE))>10000) {
                        xdelay = 4;
                }
		if (get_expire_mode()) {
			xdelay = 30; /* spawn_expire so may as well work harder */
		}
        }
        return xdelay;
}



int slave_isopen(void)
{
        if (slave_chan>=0) {
                if (chan_isopen(slave_chan)) {
                        return TRUE;
                }
                chan_close(slave_chan);
                slave_chan = -1;
        }
        emsg("Slave channel to master closed, trying to re-open\n");
        slave_open();
        if (slave_chan < 0) return FALSE;
        return TRUE;
}
char *slave_portn(int n);
void nntp_setmaster(int x);

char *slave_port(void);
void slave_open(void)
{
        char master[BFSZ];
        char xport[BFSZ];
        char bf[BFSZ];

	char myname[BFSZ];

	strcpy(myname,"127.0.0.1");
	if (strlen(config_get(C_BIND_IN))>0) strcpy(myname,config_get(C_BIND_IN));
	if (strlen(config_get(C_MASTER_ADDRESS))>0) strcpy(myname,config_get(C_MASTER_ADDRESS));

        strcpy(xport,slave_portn(0));
        lprintf(master,BFSZ-1,"%s:%s",myname,xport);
        slave_chan = chan_open(master);
        nntp_setmaster(slave_chan);
        if (slave_chan<0) {
                emsg("Could not open connection to master %s\n",master);
                return;
        }
        lprintf(bf,BFSZ-1,"xdslave %s setslave %d\r\n",conf_getpass(),slave_n());
        chan_settimeout(slave_chan,60*60*24*365);
        chan_write_s(slave_chan,bf);
        imsg("Opening connection to master %s\n",master);
}
char *slave_port(void)
{
        return slave_portn(slave_i);
}
char *slave_portn(int n)
{
        static char bf[BFSZ];
        static char bf2[BFSZ];
        char *s;
        strcpy(bf,config_get(C_SLAVE_PORTS));
        if (atoi(config_get(C_SLAVE_N))<2) return "";
        if (strlen(bf)==0) strcpy(bf,"7500");
/*      imsg("Slave ports start at (%s) \n",bf); */
        s = strtok(bf," \n\t"); if (s==NULL) s = "7500";
        lprintf(bf2,BFSZ-1,"%d",atoi(s) + n);
        return bf2;
}

void slave_xhlookup(int chan, char *in)
{
        int grp,num;
        char xchan[BFSZ];
        char msgid[BFSZ];
        char bf[BFSZ];
        ncpy(xchan,get_word(in,1),BFSZ-1);
        ncpy(msgid,get_word(in,2),BFSZ-1);
        
        grp = -1;
		stat_start(X_LOOKUP);
        if (!hist_find(msgid,&grp,&num)) {
                lprintf(bf,BFSZ-1,"xhresult %s %s $unknown$ 0\r\n",xchan,msgid); 
    	    imsg("hist: xhlookup not found %s\n",msgid);
        } else {
                lprintf(bf,BFSZ-1,"xhresult %s %s %s %d\r\n",xchan,msgid,group_name(grp),num);
    	    imsg("hist: xhlookup found it %s %d\n",group_name(grp),num);
        }
        chan_write_s(chan,bf);
		stat_end(X_LOOKUP);
}
void slave_xhresult(int schan, char *in)
{
        char msgid[BFSZ];
        char group[BFSZ];
        char bf[BFSZ];
        int num;
        int c; 

        c = atoi(get_word(in,1));
        ncpy(msgid,get_word(in,2),BFSZ-1);
        ncpy(group,get_word(in,3),BFSZ-1);
        num = atoi(get_word(in,4));
        nntp_setpending(c,FALSE);
        if (!chan_isopen(c)) return;
        if (slave_cmd[c]<=0) {
                emsg("Got response for timed out request on chan %d\n",slave_cmd[c]);
                return; 
        }
        if (strcmp(group,"$unknown$")==0) {
            chan_write_s(c,"430 no such article found\015\012");
            slave_cmd[c] = -1;
            return;
        } 
        lprintf(bf,BFSZ-1,"cmd %s:%d",group,num);
        set_failmid(TRUE);
             if (slave_cmd[c]==SL_HEAD) ncmd_head(c,bf);
        else if (slave_cmd[c]==SL_BODY) ncmd_body(c,bf);
        else if (slave_cmd[c]==SL_ARTICLE) ncmd_article(c,bf);
        else if (slave_cmd[c]==SL_STAT) ncmd_stat(c,bf);
        slave_cmd[c] = -1;
        set_failmid(FALSE);
}
void slave_xdpost(int schan, char *in)
{
        char rest[BFSZ];
        char bf[BFSZ];
        int c; 
        int e;
        char *s;
        c = atoi(get_word(in,1));
        e = atoi(get_word(in,2));
        
        ncpy(rest,get_rest(in,2),BFSZ-1);
        imsg("Got post response from [%d] %d %s\n",c,e,rest);
        s = strchr(rest,'\n'); if (s!=NULL) *s = 0;
        s = strchr(rest,'\r'); if (s!=NULL) *s = 0;
        nntp_setpending(c,FALSE);
        if (!chan_isopen(c)) return;
        if (slave_cmd[c]<=0) {
                emsg("Got response for timed out post on chan %d\n",slave_cmd[c]);
                return; 
        }
        lprintf(bf,BFSZ-1,"%s\r\n",rest);
        chan_write_s(c,bf);
        slave_cmd[c] = -1;
}
void slave_lookup(int chan, char *msgid, int cmd)
{
        char bf[BFSZ];
        nntp_setpending(chan,TRUE);
        if (slave_msgid[chan]==NULL) slave_msgid[chan] = mymalloc2(200,M_CMD);
        ncpy(slave_msgid[chan],msgid,199);
        slave_cmd[chan] = cmd;
        slave_xtimeout[chan] = lib_gmtime() + 30;
        if (!slave_isopen()) return;
        lprintf(bf,BFSZ-1,"xhlookup %d %s\r\n",chan,msgid);
        chan_write_s(slave_chan,bf);
}       
void slave_tellmaster_cached(int g)
{
        char bf[BFSZ];
        if (!is_slave()) return;
        if (!slave_isopen()) return;
	imsg("Telling master to cache %s\n",group_name(g));
        lprintf(bf,BFSZ-1,"xdslave %s xcached %s\r\n",conf_getpass(),group_name(g));
        chan_write_s(slave_chan,bf);
}
void slave_tellmaster_buildxover(int g)
{
        char bf[BFSZ];
        if (!is_slave()) return;
        if (!slave_isopen()) return;
	imsg("Telling master to cache %s\n",group_name(g));
        lprintf(bf,BFSZ-1,"xdslave %s buildxover %s\r\n",conf_getpass(),group_name(g));
        chan_write_s(slave_chan,bf);
}
void slave_xoverhit(int g)
{
        char bf[BFSZ];
        if (is_readmaster()) {
		imsg("slave: readonly slave master so tell real master to xhit (%s)\n",group_name(g));
                readonly_xhit(g);
        }
        if (!is_slave()) return;
        if (!slave_isopen()) {
		imsg("Cannot tell master about xover hit as link isn't open %s\n",group_name(g));
		return;
	}
        lprintf(bf,BFSZ-1,"xdslave %s xhit %s\r\n",conf_getpass(),group_name(g));
        chan_write_s(slave_chan,bf);
	imsg("slave: tell master to xhit %s\n",group_name(g));
}
void slave_xactive(int xchan, char *in)
{
        int from,to,range,iscached;
        int gid,g;
        char modstr[BFSZ];

        gid = atoi(get_word(in,1));
        g = gid_to_group(gid);
        if (g<0) {emsg("slave: invalid gid %d, restart slaves\n",gid); return;}
        from = atoi(get_word(in,2));
        to = atoi(get_word(in,3));
        range = atoi(get_word(in,4));
        iscached = (int) *(get_word(in,5));
        ncpy(modstr,get_word(in,6),2);
        
        dmsg("slave: xactive %s %d-%d, %d items, %c\n",group_name(g), from,to,range,iscached);
        group_setstart(g,from);
        group_setend(g,to);
        group_setrange(g,range);
        group_setcachedc(g,iscached);
        group_setmod(g,modstr);
        db_free_cache(g,"xactive");

	/* These are experiements */
	/*db_flush_quick();*/
	/*db_flush2();*/

}       
void slave_newgroup(int xchan, char *in)
{
        char grp[BFSZ],user[BFSZ],mod[BFSZ];
        int gid;
        strcpy(user,"slave");
        ncpy(grp,get_word(in,3),BFSZ-1);
        ncpy(mod,get_word(in,4),BFSZ-1);
        gid = atoi(get_word(in,5));
        imsg("Creating new group (%s) gid=%d\n",grp,gid);
        group_slavegid(gid);
        group_new(grp,user,mod,"");
}       
#define MAX_SLAVE 200
static int slaves_out[MAX_SLAVE];
static int slaves_pid[MAX_SLAVE];
static int nslaves;
void slave_xbucket(int chan, char *in);
void slave_flushtcp(void);
void slave_xcached(int chan, char *in);
void slave_xcmd(int chan, char *in)
{
        char cmd[BFSZ];
        char passwd[BFSZ];
	char reason[BFSZ];

        ncpy(passwd,get_word(in,1),BFSZ-1);
/*        if (strcmp(passwd,conf_getpass())!=0) {*/
	if(!conf_pass_try(passwd,chan,FALSE,FALSE,reason)) {
                emsg("slave: cmd=(%.200s) password failed (%s) (%s) %s \n",in,passwd,conf_getpass(),reason);
                return;
        }
        chan_setauth(chan,TRUE);

        ncpy(cmd,get_word(in,2),BFSZ-1);
        if (strcmp(cmd,"setslave")==0) slave_xsetslave(chan,in);
        else if (strcmp(cmd,"flushstop")==0) slave_flushstop(chan,in);
        else if (strcmp(cmd,"flushstart")==0) slave_flushstart(chan,in);
        else if (strcmp(cmd,"shutdown")==0) slave_xshutdown(chan,in);
        else if (strcmp(cmd,"xhit")==0) slave_xhit(chan,in);
        else if (strcmp(cmd,"xbucket")==0) slave_xbucket(chan,in);
        else if (strcmp(cmd,"xcached")==0) slave_xcached(chan,in);
        else if (strcmp(cmd,"buildxover")==0) slave_buildxover(chan,in);
        else if (strcmp(cmd,"db_flush")==0) db_flush();
        else if (strcmp(cmd,"newgroup")==0) slave_newgroup(chan,in);
        else imsg("xdslave unknown command (%s) \n",cmd);
}
void slave_xhit(int chan, char *in)
{
        char group[BFSZ];
        int g;
        ncpy(group,get_word(in,3),BFSZ-1);
        g = group_find(group);
        if (g<0) {
                imsg("slave: xhit failed group (%s) \n",group);
                return;
        }
        imsg("Xover hit from slave (%s) \n",group);
        group_hitxover(g);
}
void slave_xcached(int chan, char *in)
{
        char group[BFSZ];
        int life=atoi(config_get(C_LIFE));
        int g;
        ncpy(group,get_word(in,3),BFSZ-1);
        g = group_find(group);
        if (g<0) {
                imsg("slave: xcached failed group (%s) \n",group);
                return;
        }
/*      imsg("Xcache hit from slave (%s) \n",group);*/

        if (!group_iscached(g)) {
                 if (!group_inupdate(g)) {
                        imsg("Starting an initial suck for group (%s) which slave wants to read\n",group_name(g));
                        suck_queue_anyone(group_name(g),0);
                  }     
                  group_setupdate(g,TRUE);
        }

        if (group_getlife(g)<life) group_setlife(g,life);
        group_setcached(g,TRUE);
}
void slave_buildxover(int chan, char *in)
{
        char group[BFSZ];
        int g;
        ncpy(group,get_word(in,3),BFSZ-1);
        g = group_find(group);
        if (g<0) {
                imsg("slave: buildxover failed group (%s) \n",group);
                return;
        }
	imsg("slave: asked us to re-build xover data for (%s) \n",group);
	xidle_sort(g);
}
void slave_xbucket(int chan, char *in)
{
        int n,dsk,pile;
        n = atoi(get_word(in,3));
        dsk = atoi(get_word(in,4));
        pile = atoi(get_word(in,5));
        db_slave_bucket(n,dsk,pile);
}
void slave_flushstart(int chan, char *in)
{
        emsg("slave: Master said we can start again :-)\n");
        /* Should send back confirmation message for master */
}
void db_flush2(void);
void slave_flushstop(int chan, char *in)
{
        /* Master process wants us to close the database and wait*/
        
        db_flush2();
        db_clear_cache("slave_flushstop");
	hist_close();
        emsg("slave: master process asked us to close database files, waiting...\n");
        emsg_close();
        nntp_chanwait(slave_chan,300); /*Wait for upto 5 minutes */
        emsg("slave: Starting again\n");
}
void slave_xshutdown(int chan, char *in)
{
        wmsgf("slave: Master asked us to shut down\n");
        dnews_ex("Exit command");
        exit(1);
}
int is_master(void)
{
        if (xismaster) return TRUE;
        if (nslaves>0) return TRUE;
        return FALSE;
}
int is_mastermaster(void)
{
        if (slave_i==0) if (!is_reader()) return TRUE;
        return FALSE;
}
void slave_xsetslave(int chan, char *in)
{
        int i;
        for (i=0; i<nslaves; i++) {
                if (slaves_out[i]<0) {
                        slaves_out[i] = chan;
                        goto doneit;
                }
        }
        i = nslaves;
        slaves_out[nslaves++] = chan;           

doneit: 
        slaves_pid[i] = atoi(get_word(in,3));
        imsg("slave: Connection from slave on port %d, pid=%d\n",chan,slaves_pid[i]);
        chan_settimeout(chan,60*60*24*365);
}
static int dmulti_chan = -1;
void nntp_setip(int c, char *ip);
/* Old verion           ip.address 
        new version     ip.address lcl.ip.addres tellnes_pass 
*/
char *nntp_getwelcome(int chan);
void slave_xdrealip2(int chan, char *in)
{
	slave_xdrealip(chan,in);
	if (chan_isopen(chan)) {
		chan_setdom(chan,NULL);
		chan_write_s(chan,nntp_getwelcome(chan));
	}
}
void slave_xdrealip(int chan, char *in)
{
        char ip[BFSZ],lcl[BFSZ],pass[BFSZ];
        static int gotit;
        int lport,rport;
        lport = rport = 0;
        strcpy(lcl,"127.0.0.1");
        ncpy(ip,get_word(in,1),BFSZ-1);
        strcpy(pass,"");
        if (get_word(in,3)!=NULL) {
                ncpy(lcl,get_word(in,2),BFSZ-1);
                ncpy(pass,get_word(in,3),BFSZ-1);
                if (get_word(in,4)!=NULL) {
                        lport = atoi(get_word(in,4));
                        rport = atoi(get_word(in,5));
			imsg("pident: lclip=%s rmtip=%s lport=%d rport=%d\n",lcl,ip,lport,rport);
                }
        }
        if (strlen(pass)>0) gotit = TRUE;
        if (gotit) {
               if (strcmp(pass,conf_getpass())!=0) {
                       emsg("dmulti sent wrong tellnews password (%s)\n",pass);
                       dmsg("lib: (%s) != (%s) \n",pass,conf_getpass());
                       return;
               } 
        }
	if (config_true(C_LOG_USERS)) wmsg("users: %s realip %s\n",chan_getipnum(chan),ip);
	imsg("Multi link from (%s) to (%s)\n",ip,lcl);
        nntp_setlocalip(chan,lcl);      
        nntp_setip(chan,ip);
        nntp_setports(chan,lport,rport);
	nntp_setmulti(chan,TRUE);
 	if (config_true(C_PIDENT)) {
	        if (atoi(config_get(C_SLAVE_N))>0) {
	                id_ask(chan);
	        }
	}
}
void slave_xdmulti(int chan, char *in)
{

        chan_write_s(chan,"Hi there dmulti, nice to hear from you\r\n");
        imsg("slave: Connection from dmulti on port %d\n",chan);
        chan_settimeout(chan,60*60*24*365);
        dmulti_chan = chan;
}
static char fixbf[2000];

void slave_group_new(char *s, char *user, char *mod, char *title, int gid)
{
        char bf[BFSZ];
        static char pass[200];
        if (nslaves==0) return;
        if (strlen(pass)==0) strcpy(pass,conf_getpass());
        lprintf(bf,BFSZ-1,"xdslave %s newgroup %s %s %d\r\n"
                ,pass,s,mod,gid);
        strcat(fixbf,bf);
        if (strlen(fixbf)>1500) slave_flushsnd();
}
#define BUCKET_CHANGES file_work("bkt_change.dat")
void slave_changes_read(void)
{
    char bf[BFSZ];
    char *p[100];
    int np;
    FILE *f;
    int n,dsk,pile;
    f = fopen(BUCKET_CHANGES,"r");
    if (f==NULL) return;
    for (;!feof(f);) {
	   if (fgets(bf,BFSZ-1,f)==NULL) break;
	   np = token_split(bf,p," \r\n\t",20);
	   if (np>=4) {
	       if (strcmp(p[0],"xbucket")!=0) emsg("readonly got bad input (%s)\n",p[0]);
		imsg("readonly: got bkt change from master %s %s %s\n",p[1],p[2],p[3]);
	        n = atoi(p[1]);
		dsk = atoi(p[2]);
		pile = atoi(p[3]);
	        db_slave_bucket(n,dsk,pile);
	   }
    }
}
void slave_bucket_clear(void)
{
    remove(BUCKET_CHANGES);
}
void slave_bucket(int n, int dsk, int pile)
{
        char bf[BFSZ];
        static char pass[200];
	int rn = atoi(config_get(C_READONLY_N));

	if (rn>0) {
	    FILE *f;
	    f = fopen(BUCKET_CHANGES,"a");
	    if (f!=NULL) {
		fprintf(f,"xbucket %d %d %d\n",n,dsk,pile);
    		fclose(f);
	    }
	}
        if (nslaves==0) return;
        if (strlen(pass)==0) strcpy(pass,conf_getpass());
        lprintf(bf,BFSZ-1,"xdslave %s xbucket %d %d %d\r\n"
                ,pass,n,dsk,pile);
        strcat(fixbf,bf);
        if (strlen(fixbf)>1500) slave_flushsnd();
}
void slave_fixgroup(int grp, int from, int to, int range, int cflag,int mod)
{
        char bf[BFSZ];
        if (nslaves==0) return;
        if (xactive_disable) return;
	  if (config_true(C_SLAVE_BYFILE)) return;

        if (mod<32) mod = 'y';
        if (mod>127) mod = 'y';
        lprintf(bf,BFSZ-1,"xactive %d %d %d %d %c %c\r\n",group_getgroupid(grp),from,to,range,cflag,mod);
        strcat(fixbf,bf);
        if (strlen(fixbf)>1500) slave_flushsnd();
}
void slave_flushsnd(void)
{
        if (fixbf[0] == 0) return;
        db_flush_quick(); /* Make sure files on disk match what we are telling the slaves */
	slave_tokids(fixbf);
        strcpy(fixbf,"");
        slave_flushtcp();
}
void slave_tokids(char *bf)
{
        int i;
        if (nslaves==0) return;
        for (i=0; i<nslaves; i++) {
          if (slaves_out[i] >= 0) {
            if (chan_isopen(slaves_out[i])) {
                /* imsg("Sending to kids[%d] %s",slaves_out[i],bf);*/
                  chan_write_s(slaves_out[i],bf);
            } else {
                emsg("slave: Lost connection from SLAVE on channel %d\n",slaves_out[i]);
                slaves_out[i] = -1;
            }
          }
        }               
}
#define MAX_WAIT 190
void slave_flushtcp(void)
{
        int i;
        char bf[BFSZ];
        char bf2[BFSZ];
        char xbf[BFSZ];
        int count = 0;
        int n;
        int done,bad=0;
        if (nslaves==0) return;

again:
        done = TRUE;
        for (i=0; i<nslaves; i++) {
          if (slaves_out[i] >= 0) {
            if (chan_isopen(slaves_out[i])) {
                /* imsg("Sending to kids[%d] %s",slaves_out[i],bf);*/
                if (!nntp_write_flush(slaves_out[i])) {
                        bad = i;
                        if (count>2) emsg("Waiting on slave on chan %d, %d/%d\n",slaves_out[i],count,MAX_WAIT);
                        done = FALSE;
                        if (count>20) {
                                n = nntp_read(slaves_out[i],xbf,BFSZ-10); 
                                for (;n>0;) {
                                        ncpy(bf2,xbf,40);
                                        emsg("Clearing blocked slave data %d bytes %s\n",n,bf2);
                                        n = nntp_read(slaves_out[i],xbf,BFSZ-10); 
                                }
                        }
                }
            }
          }
        }               
        if (!done) {
                count++;
                if (count>MAX_WAIT) { /* increased for xsorting on the fly */
                        if ((bad<0) || (bad>100)) return;
                        emsg("FATAL, Slave not responding, attempting to kill it %d, id=%d\n",bad,slaves_pid[bad]);
                        lprintf(bf,BFSZ-1,"xdslave %s kill %d\r\n",conf_getpass(),slaves_pid[bad]);
                        if (dmulti_chan>=0) chan_write_s(dmulti_chan,bf);
                        return;                 
                }
                sleep(1);
                goto again;
        }
}
void slave_closing(int chan)
{
        int i;
        for (i=0; i<nslaves; i++) {
                if (slaves_out[i]==chan) {
                        emsg("slave: Lost connection from SLAVE on channel %d\n",slaves_out[i]);
                        slaves_out[i] = -1;
                }
        }
}
void slave_shutdown(void)
{
        char bf[BFSZ];
        FILE *f;
        if (!is_master()) return;
        lprintf(bf,BFSZ-1,"xdslave %s norestart\r\n",conf_getpass());
        if (dmulti_chan>=0) {
                chan_write_s(dmulti_chan,bf);
                imsg("Tell master we are shutting down %d\n",dmulti_chan);
		nntp_write_flush(dmulti_chan);
        }
        sleep(2); /* Give dmulti a chance to see the command */
        f = fopen(DMULTI_EXIT,"w");
        if (f!=NULL) {
                fprintf(f,"shutting requested");
                fclosep(f);
        }
        lprintf(bf,BFSZ-1,"xdslave %s shutdown\r\n",conf_getpass());
        imsg("Waiting for slaves to exit...\n");
        slave_tokids(bf);
        sleep(2); /* Give slaves a chance to die cleanly */
        if (dmulti_chan>=0) chan_write_s(dmulti_chan,bf);
        sleep(2);
}
void slave_killslaves(void)
{
        char bf[BFSZ];
        if (nslaves==0) return;
        lprintf(bf,BFSZ-1,"xdslave %s shutdown\r\n",conf_getpass());
        slave_tokids(bf);
}
void slave_reload(void)
{
        char bf[BFSZ];
        if (nslaves==0) return;
        slave_flushsnd();
        lprintf(bf,BFSZ-1,"fcmd %s reload\r\n",conf_getpass());
        slave_tokids(bf);
}
#define FILE_FLUSH file_work("flush.now")
void slave_flush(int t) 
{
	slave_flush_cmd(t,"flushnow");
}
void slave_flush_cmd(int t, char *mode) 
{
        FILE *f;
	int rn = atoi(config_get(C_READONLY_N));
	int n = atoi(config_get(C_SLAVE_N));
	/*
	if (nslaves==0 && (rn==0) ) {
		if (n>0) emsg("FATAL ERROR, SLAVE_N IS > 0 BUT NO SLAVE CONNECTIONS FROM MASTER EXIST s=%d\n",slave_n());
		return;
	}
	*/
	n--;
	if (rn==0) if (n!=nslaves) wmsg("Thats odd, nslaves=%d, slave_n-1=%d \n",nslaves,n);
        if (t) {
                imsg("Asking slaves to pause\n");
                f = fopen(FILE_FLUSH,"w");
                if (f==NULL) {
                        filemsg("Could not create %s\n",FILE_FLUSH);    
                        return;
                }
                fprintf(f,"%s",mode);
                fclosep(f);
        } else {
                imsg("Asked slaves to unpause\n");
                if (remove(FILE_FLUSH)!=0) {
                        filemsg("Remove(%s) failed\n",FILE_FLUSH);
                        sleep(2);
                        remove(FILE_FLUSH);
                        remove(FILE_FLUSH);
                }
        }
        if (t) sleep(2); /* Give files a chance to close */
}
void slave_checkpause(void)
{
        int i;
	int onlyclose=FALSE;
	char bf[BFSZ];
	FILE *f;
        /* Master process wants us to close the database and wait*/

	if (config_true(C_DISKIO_MIN)) return;

        if (lib_exists(FILE_FLUSH)) {
                if (!is_readonly()) {
                        remove(FILE_FLUSH);
                        return;
                }
		strcpy(bf,"");
		f = fopen(FILE_FLUSH,"r");
		if (f!=NULL) {
			fgets(bf,100,f);
			fclose(f);
		}
		if (strstr(bf,"onlyclose")!=NULL) onlyclose = TRUE;
                db_flush2();
		item_xover_close();
		hist_close();
                if (!onlyclose) db_clear_cache("slave_checkpause");
                emsg("slave: master process asked us to close database files, waiting...\n");
		emsg_close();
                for (i=1; i<1200; i++) {
                        sleep(1);
                        if (!lib_exists(FILE_FLUSH)) break;
                        if (i>600) remove(FILE_FLUSH);
                }
                emsg("slave: Starting again 2\n");
        } else dmsg("slave: (%s) does not exist %s\n",FILE_FLUSH,strerror(errno));
}


void xslave_flush(int t) 
{
        char bf[BFSZ];
        if (nslaves==0) return;
        if (t) {
                lprintf(bf,BFSZ-1,"xdslave %s flushstop\r\n",conf_getpass());
                /* Should block until slaves tell us they are ready. */
                /*  Need to write to sockets and then read until we get the acknowledgement */
        } else {
                lprintf(bf,BFSZ-1,"xdslave %s flushstart\r\n",conf_getpass());
        }
        slave_tokids(bf);
        slave_flushtcp();
        if (t) sleep(2); /* Give files a chance to close */
}
int slave_post(int chan, String *xitem)
{
        char bf[BFSZ];
        if (!is_slave()) return FALSE;
        if (!slave_isopen()) return FALSE;
        lprintf(bf,BFSZ-1,"xdtakethis %d %s %s %s\r\n",chan,get_ahead(xitem,"Message-ID:"),chan_getipnum(chan),nntp_localip(chan));

	chan_write_s(slave_chan,bf);
        chan_write(slave_chan,xitem);
        chan_write_s(slave_chan,".\r\n");
        lprintf(bf,BFSZ-1,"xdslave %s db_flush\r\n",conf_getpass());
        chan_write_s(slave_chan,bf);

        nntp_setpending(chan,TRUE);
        slave_cmd[chan] = SL_POST;
        slave_xtimeout[chan] = lib_gmtime() + 300; /* wait 5 minutes max */
        db_flush2(); /* get rid of dbi flushing */
        return TRUE;
}
int slave_dbflush(void)
{
        char bf[BFSZ];
        if (!is_readonly()) return FALSE;
        if (!slave_isopen()) return FALSE;
        db_flush2(); /* get rid of dbi flushing */
        lprintf(bf,BFSZ-1,"xdslave %s db_flush\r\n",conf_getpass());
        chan_write_s(slave_chan,bf);
        return TRUE;
}
void ncmd_timeout(int c)
{
        /* If request older than timeout, then give up */
        if (slave_cmd[c]<=0) return;
        if (slave_xtimeout[c] < lib_gmtime()) {
                emsg("slave: request type %d timed out, master not responding\n",slave_cmd[c]);
                slave_timeout(c);
        }
}
void slave_timeout(int c)
{
        switch (slave_cmd[c]) {
                case SL_HEAD:
                case SL_BODY:
                case SL_ARTICLE:
                case SL_STAT:
                    chan_write_s(c,"430 no such article found\015\012");
                    break;
                case SL_POST:
                    chan_write_s(c,"441 post failed, master did not respond\015\012");
                    break;
        }
        nntp_setpending(c,FALSE);
        slave_cmd[c] = -1;
}



/* talk to master stuff */

int tell_master(char *cmd) 
{
    /* issue a command to the 'master' and wait for it to complete */
    FILE *f;
	int n=0;
    if (!get_expire_mode()) {
        /*imsg("expirer: tried to tell_master when not in expire mode\n");*/
        return FALSE;
    }
    imsg("expirer: tell_master(%s)\n",cmd);
    f=fopen(file_work("tellmast.dat"),"r");
    if (f!=NULL) {
        emsg("expirer: tellmast.dat already exists\n");
        fclosep(f);
        return FALSE;
    }
    f = fopen(file_work("tellmast.dat"),"w");
    if (f==NULL) {
        emsg("expirer: couldn't create tellmast.dat\n");
        return FALSE;
    }
    if (fprintf(f,cmd)<0) {
        emsg("expirer: error writing to tellmast.dat\n");
        if (fclosep(f)==EOF) emsg("expirer: couldn't close tellmast.dat (%s)\n",strerror(errno));
        return FALSE;
    }
    if (fclosep(f)==EOF) {
        emsg("expirer: couldn't close tellmast.dat (%s)\n",strerror(errno));
        return FALSE;
    }    
    else {
	n = 0;
        while (TRUE) {
            f=fopen(file_work("tellmast.dat"),"r");
            if (f==NULL)
                return TRUE;
            else {
                if (fclosep(f)==EOF) emsg("expirer: couldn't close tellmast.dat (%s)\n",strerror(errno));
                lib_sleep(1);
		n++;
		if ((n%600) == 30) {
			imsg("Expire waiting on dnews master, it's taking a long time...\n");
		}
            }
        }
    }
    return FALSE;
}

char* check_master_command(void) 
{
    FILE *f;
    char *cmd;
    static char buf[BFSZ];
    
    f=fopen(file_work("tellmast.dat"),"r");
    if (f==NULL) {
        /* imsg("master: checked for command but couldn't open tellmast.dat (%s)\n",strerror(errno)); */
        return NULL;
    }
    else {
        cmd = fgets(buf,BFSZ,f);
        if (cmd==NULL) {
            emsg("master: could not read expire command\n");
	    sleep(1);
	    fseek(f,0,SEEK_SET);
	    cmd = fgets(buf,BFSZ,f);
	    if (cmd==NULL) {		    
	            if (fclosep(f)==EOF) emsg("error: tell master: couldn't close tellmast.dat\n");
	            return NULL;
	    } else emsg("master: Got the command on the second read\n");
        }
        if (fclosep(f)==EOF) emsg("expirer: couldn't close tellmast.dat (%s)\n",strerror(errno));
        /*imsg("master: got command from expire process (%s)\n",cmd);*/
        return cmd;
    }
}

int signal_command_done(void) 
{
    int i;
    
    for(i=0;i<60;i++) {
        if (remove(file_work("tellmast.dat"))==0) return TRUE;
        lib_sleep(1);
    }
    emsg("master: couldn't signal command completion - could not remove tellmast.dat (%s)\n",strerror(errno));
    return FALSE;
}

int signal_wait(void)
{
    /* Wait for continue command */
    int i;
    
    for(i=0;i<260;i++) {
        if (lib_exists(file_work("tellmast.dat"))) {
            for(i=0;i<60;i++) {
                if (remove(file_work("tellmast.dat"))==0) return TRUE;
                lib_sleep(1);
            }
            emsg("master: signal_wait timed out trying to remove tellmast.dat (%s)\n",strerror(errno));     
            return FALSE;  
        }
        lib_sleep(1);
    }
    emsg("master: signal_wait never found tellmast.dat (%s)\n",strerror(errno));
    return FALSE;
}
void remove_tellmast_dat(void)
{
    int i;
    for(i=0;i<60;i++)
    {
        if (remove(file_work("tellmast.dat"))==0) {
            imsg("master: removed previous existing tellmast.dat\n");
            return;
        } else {
		if (errno==ENOENT) return;
	}
        lib_sleep(1);
    }
    /*imsg("master: tellmast.dat not found, good thing (%s)\n",strerror(errno));*/
}

int spawn_dnews_expire(void)
{
    char cmd[BFSZ];
	int r;
//	int phandle;
    lprintf(cmd,BFSZ-1,"%s -expire",file_exe("dnews"));
    r = lib_spawn_cmd(cmd);
    return r;
}




/*

  Lets try and detect sucking 

	suck 

mode reader
stat nnn
(1...n)
article
next
article 
next (no parameters)


*/


typedef struct MLOGIN {time_t created; char *user; char *userip; char *machine; int n; int chan; struct MLOGIN *next;} Mlogin;
#define MHASH 1000
static Mlogin *mhash[MHASH];
int mcount_free(Mlogin *m);

int mlogin_hash(char *s)
{
	return str_hash(s,MHASH);
}
int mcount_reset(void)
{
	Mlogin *m,*mnext;
	int h;
	for (h=0; h<MHASH; h++) {
		for (m=mhash[h];m!=NULL;m=mnext) {
			mnext = m->next;
			mcount_free(m);
		}
		mhash[h] = NULL;
	}
	return TRUE;
}
int mcount_login(int chan,char *pchan,char *p1,char *p2, char *userip)
{
	int h;
	char bf[BFSZ];
	char *wrong="count";
	int nmach;
	int max =2;
	int nbadip=0;
	int timeout;
	Mlogin *m,*lastm;
	char was[BFSZ];
	time_t now;
	now = time(NULL);
	timeout = atoi(config_get(C_WIDEAREA_TIMEOUT));
	if (timeout==0) timeout = 1800; /* 30 minutes by default */
	
	max = atoi(config_get(C_WIDEAREA_MAX));
	if (max==0) max = 2;
	nmach = 0;
	strcpy(was,"");
	imsg("mlogin_add(%.100s %.100s,%.100s,%.100s)\n",pchan,p1,p2,userip);
	h = mlogin_hash(p1);
	for (m=mhash[h];m!=NULL;m=m->next) {
		if ((now-m->created)>timeout) continue;
		if (strcmp(p1,m->user)==0) {
			nmach++;
			if (strcmp(userip,m->userip)!=0) {
				nbadip++;
				if (config_true(C_WIDEAREA_IPCHECK)) {
					wrong = "wrong_host";
					if (strlen(was)>0) ncat(was,",",BFSZ-1);
					ncat(was,m->userip,BFSZ-1);
					ncat(was,"@",BFSZ-1);
					ncat(was,m->machine,BFSZ-1);
					lastm = m;
				}
			}
		}
	}
	imsg("mcount user (%.100s) nmach %d max %d h=%d nbadip=%d\n",p1,nmach,max,h,nbadip);
	h = mlogin_hash(p1);
	for (m=mhash[h];m!=NULL;m=m->next) {
		if ((now-m->created)>timeout) continue;
		if (strcmp(p1,m->user)==0) {
			if (strcmp(p2,m->machine)!=0) {
				if (nmach>=max) {
					wrong = "wrong_host";
					if (strlen(was)>0) ncat(was,",",BFSZ-1);
					ncat(was,m->machine,BFSZ-1);
					lastm = m;
				}
			} else {
				m->n++;
				goto isok;
			}
		}
	}
	if (strlen(was)>0) {
		m = lastm;
		goto isok;
	}
	m = mymalloc2(sizeof(Mlogin),M_CMD);
	m->chan = chan;
	m->user = mystrdup(p1,SD_CMD);
	m->machine = mystrdup(p2,SD_CMD);
	m->userip = mystrdup(userip,SD_CMD);
	m->created = time(NULL);
	m->n=1;
	m->next = mhash[h];
	mhash[h] = m;
isok:
	if (strlen(was)==0) lprintf(was,BFSZ-1,"%s@%s",m->userip,m->machine,100);

	lprintf(bf,BFSZ-1,"%s=%d chan=%s user=%.100s on=%.100s\r\n",wrong,m->n,pchan,m->user,was);
	chan_write_s(chan,bf);
	return TRUE;
}
int mcount_free(Mlogin *m)
{
	mystrfree(m->user,SD_CMD);
	mystrfree(m->userip,SD_CMD);
	mystrfree(m->machine,SD_CMD);
	myfree2(m,M_CMD);
	return TRUE;
}
int mcount_clearchan(int chan)
{
	Mlogin *m,*mnext,**last;
	int h;
	time_t now;
	int timeout;
	now = time(NULL);
	timeout = atoi(config_get(C_WIDEAREA_TIMEOUT));
	if (timeout==0) timeout = 1800; /* 30 minutes by default */

	for (h=0; h<MHASH; h++) {
		last = &mhash[h];
		for (m=mhash[h];m!=NULL;m=mnext) {
			mnext = m->next;
			if (m->chan==chan) {
				imsg("mcount: removing %.100s %.100s\n",m->machine,m->user);
				mcount_free(m);
				*last = m->next;
				continue;
			}
			if (now-m->created>timeout) {
				imsg("mcount: removing expired %.100s %.100s\n",m->machine,m->user);
				mcount_free(m);
				*last = m->next;
				continue;
			}
			last = &(m->next);
		}
	}
	return TRUE;
}
int mcount_logout(int chan,char *p1, char *p2)
{
	int h;
	char bf[BFSZ];
	int nnow = 0;
	char mach[BFSZ];
	Mlogin *m;
	Mlogin **mlast;
	imsg("mcount_logout(%.100s,%.100s)\n",p1,p2);
	h = mlogin_hash(p1);
	mlast = &mhash[h];
	ncpy(mach,p2,BFSZ-1);
	for (m=mhash[h];m!=NULL;m=m->next) {
		if (strcmp(p1,m->user)==0) {
			if (strcmp(p2,m->machine)==0) {
				m->n--;
				nnow = m->n;
				if (m->n <= 0) {
					*mlast = m->next;
					mcount_free(m);
					break;
				}
			} else {
				nnow = m->n;
				ncpy(mach,m->machine,BFSZ-1);
			}
		}
		mlast = &(m->next);
	}
	lprintf(bf,BFSZ-1,"count=%d user=%.100s on=%.100s\r\n",nnow,p1,mach);
	chan_write_s(chan,bf);
	return TRUE;
}

int failed_pass_attempts=0;
int failed_pass_last_attempt;
char *chan_getipnum(int);

int conf_pass_force(void)
{
	if(time(NULL)>failed_pass_last_attempt+60)
		failed_pass_attempts=0;
	return failed_pass_attempts>=10;
}
int conf_pass_try(char *pass,int chan,int logfail,int encrypted, char *reason)
{
	char passmask[BFSZ];
	int accept=0;
	static char lastip[BFSZ];

	strcpy(passmask,config_get(C_TELLNEWS_PASS_MASK));

	lprintf(reason,200,"Not from tellnews_pass_mask (%s)",chan_getipnum(chan));
	if(!ip_match(*passmask=='\0'?"*":passmask,chan_getipnum(chan))) {
		imsg("tellnews_pass_mask failed (%s) (%s)\n",passmask,chan_getipnum(chan));
		return 0;
	}
	strcpy(reason,"Breakin detection, restart dnews");
	if (strcmp(lastip,chan_getipnum(chan))!=0) failed_pass_attempts = 0;
	ncpy(lastip,chan_getipnum(chan),BFSZ-1);

	if(conf_pass_force()) return 0;
	strcpy(reason,"..");
	
	if(strcmp(pass,conf_getpass())==0) accept=1;
	else if(atoi(pass)==pass_hash(chan_gethash(chan),conf_getpass())) {
		imsg("Manager logged in using hashed password %s\n",pass);
		accept=1;
	}
	if(accept) {
		failed_pass_attempts=0;
		return 1;
	}
	failed_pass_last_attempt=time(NULL);
	failed_pass_attempts++;
	return 0;
}
