/* Copyright (C) 1995 NetWin World, New Zealand, All rights reserved. */
/*
	Note: channels should not be 'freed' until they are closed.

	
Count number of users who are on different ip numbers 	

*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "group.h"
#include "gstat.h"
#include "item.h"
#include "dnews.h"
#include "nntp.h"
#include "suck.h"
#include "domain.h"
#include "chan.h"
#include "sary.h"
#include "ncmd.h"
#include "lib.h"
#include "conf.h"
#include "user.h"
#include "ml.h"
void live_read(int chan, char *bf, int sz);


static int chan_xisready[MAX_CHAN];
static int chan_state[MAX_CHAN];
static int chan_reject[MAX_CHAN];
static int chan_canihaveit[MAX_CHAN];
static int chan_canpostit[MAX_CHAN];
static int chan_xinspeed[MAX_CHAN];
static int chan_xoutspeed[MAX_CHAN];
static int chan_canreadit[MAX_CHAN];
static int chan_isauth[MAX_CHAN];
static int chan_grp[MAX_CHAN];
static int chan_more[MAX_CHAN];
static int chan_isauser[MAX_CHAN];
static int chan_isaccess[MAX_CHAN];
static int chan_num[MAX_CHAN];
static int chan_nart[MAX_CHAN];
static int chan_npost[MAX_CHAN];
static int chan_ndup[MAX_CHAN];
static char chan_isinbody[MAX_CHAN];
static int chan_nreject[MAX_CHAN];
static int (*chan_fn[MAX_CHAN])(int n, char *s, int sz);

static double chan_size[MAX_CHAN];
static int chan_time[MAX_CHAN];
static int chan_lasttime[MAX_CHAN];
static int chan_tout[MAX_CHAN];
static int chan_isused[MAX_CHAN];
static int chan_dolog[MAX_CHAN];
static int chan_hash[MAX_CHAN];
static Dom *chan_curdom[MAX_CHAN];
static char chan_xisihave[MAX_CHAN];
static char *chan_user[MAX_CHAN];
static char *chan_pass[MAX_CHAN];
static char *chan_ipname[MAX_CHAN];
static char *chan_ipnum[MAX_CHAN];
static char *chan_ident[MAX_CHAN];
static char *chan_postgrps[MAX_CHAN];
static char *chan_readgrps[MAX_CHAN];
static double chan_hitrate[MAX_CHAN];
int dll_auth(char *ipname, char *ipnum, char *user, char *pass ,char *postgrps, char *readgrps, int *isauth);
int ihavein_clear(int i);
void chan_close2(int c);
void chan_writelog(int c, int isread, char *s, int len);
void user_starttime(int c);
int pop_read(int chan, char *xin, int sz);
extern int xtime_now;
int ip_match(char *wild, char *xnum);

static char *mode_str[]={"C_CMD", "C_POST", "C_GETITEM"
			 , "C_CHECK", "C_GETLIST", "C_WAIT", "C_IHAVE", 
			 "C_GETITEM_STATUS" ,"C_GETITEM_NEXT",
			 "C_STREAM_NEXT", "C_STREAM_ITEM", "C_STREAM_GROUP",
			 "C_LIVE","C_ID","C_CLOSED", "C_POP_READ", "C_FN"
			,"C_LISTGROUP"};

 
/*enum {C_CMD,C_POST,C_GETITEM,C_CHECK,C_GETLIST,C_WAIT,C_IHAVE}; */
extern int nchan; 
#define check(c) if ((c<0) || (c>=MAX_CHAN)) {emsg("chan: bad channel, PANIC %d\n",c); dnews_ex("Bad Channel");}
static char elimit[BFSZ];

int chan_isready(int c)
{
	return chan_xisready[c];
}
void chan_sethitrate(int c, double h)
{
	chan_hitrate[c] = h;
}
static char * chan_area[MAX_CHAN];
void chan_setarea(int c, char *thisarea)
{
	if (chan_area[c]==NULL) chan_area[c] = mymalloc(100);
	ncpy(chan_area[c],thisarea,80);
}
void chan_setdom(int c, Dom *d)
{
	chan_curdom[c] = d;
}
Dom *chan_getdom(int c)
{
	return chan_curdom[c];
}
char *chan_getarea(int c)
{
	if (chan_area[c]==NULL) return "none";
	return chan_area[c];
}
void chan_settimeout(int c, int t)
{
	check(c);
        chan_tout[c] = t;
}
int chan_inbody(int c);
static int log_chan[100];
static int nlog;
void chan_addlog(int c)
{
	log_chan[nlog++] = c;
}
void chan_sendlog(char *s)
{
	int i;
	char bf[BFSZ];
	char bf2[BFSZ];
	if (nlog==0) return;
	ncpy(bf,s,200);

	s = strchr(bf,'\r'); if (s!=NULL) *s = 0;
	s = strchr(bf,'\n'); if (s!=NULL) *s = 0;
	sprintf(bf2,"log %s\r\n",bf);
	for (i=0; i<nlog; i++) {
		chan_write_s(log_chan[i],bf2);
	}
}
void chan_setaccess(int c, int t)
{
	chan_isaccess[c] = t;
}
int chan_getaccess(int c)
{
	return chan_isaccess[c];
}
void chan_setrealuser(int i)
{
 	chan_isauser[i] = TRUE;
}
void chan_cleardom(void)
{
	int i;
	for (i=0; i<nchan; i++) {
		chan_curdom[i] = NULL;
	}

}
int chan_nusers(void)
{
	int i;
	int tot=0;
	static int last;
	int t;
	for (i=0; i<nchan; i++) {
		if (nntp_isopen(i)) if (chan_isauser[i]) tot++;
	}

	t = lib_gmtime();
	if ((t-last)>1200) {
		last = t;
		if (tot>0) imsg("Concurrent users %d\n",tot);
	}
	return tot;
}
void chan_setlog(int c,int x)
{
 	chan_dolog[c] = x;
}
void chan_stats_add(int c);
String *chan_status(void)
{
        static String *xbody;
        char bf[500];
	char user[BFSZ];
        int i;
	char speedbf[BFSZ];


        if (xbody==NULL) xbody = str_new();
        str_clear(xbody);
        for (i=0; i<nchan; i++) { /*loop*/
	  sprintf(speedbf,"/%d",chan_xoutspeed[i]);
	  strcpy(user,"");
	  strcpy(user,chan_getuser(i));
	  if (chan_ident[i]!=NULL) strcpy(user,chan_ident[i]);
          if (nntp_isopen(i)) {
                if (chan_ipnum[i]==NULL || chan_ipname[i]==NULL) {
                  sprintf(bf,"Chan[%d:%d]: NULL???",i,nntp_chan(i));
                } else {
                        sprintf(bf,"Chan[%d:%d] %s o=%d in=%d/s out=%d%s/s %.2g %s %s %s %s %s"
                        ,i,nntp_chan(i)
                        ,nntp_isbusy(i) ? "B" : " ",nntp_outsz(i)
			,nntp_curspeed(i)
			,nntp_curspeedout(i)
			,chan_xoutspeed[i]>0 ? speedbf : ""
                        ,chan_hitrate[i]
			,chan_ipnum[i],chan_ipname[i]
                        ,mode_str[chan_state[i]],chan_busystr(i)
			,chan_xisihave[i] ? "Incoming" : user
			);
                }
/*		if (chan_area[i]!=NULL) {sprintf(bf2,"area=%s",chan_area[i]); strcat(bf,bf2);}*/
                strcat(bf,"\015\012"); str_addsn(xbody,bf);
          } else {
	  	if (!chan_isfree(i)) {
		    sprintf(bf,"Chan[%d]: Lost channel, never closed \015\012",i);
        	    str_addsn(xbody,bf);
		}
	  }
        }

        str_addsn(xbody,"\015\012");
        return xbody;
}
char *chan_showcon(void)
{
	static char bf[500];
	char *got[5000];
	int ngot=0;
	char *ip;
        int i,j;
	int nout=0;
	int nin=0;
	int nusers=0;
	int speedin=0;
	int speedout=0;
	int unique = 0;

        for (i=0; i<nchan; i++) { /*loop*/
		if (!nntp_isopen(i)) continue;
		speedin += nntp_curspeed(i);
		speedout += nntp_curspeedout(i);
		if (chan_isihave(i)) {
			nin++;
		}
		if (chan_state[i]==C_GETITEM) nin++;
		if (chan_state[i]==C_LIVE) nout++;
		nusers ++;
		ip = chan_ipnum[i];
		for (j=0; j<ngot; j++) if (strcmp(got[j],ip)==0) goto next;
		got[ngot++] = mystrdup(ip);
		if (ngot>2000) break;
next:;
	}

	sprintf(bf,"Con %d, Unique %d, Feeds_in %d, Feeds_out %d, Total_in %dk/s, Total_out %dk/s\n",nusers,ngot,nin,nout,speedin/1000,speedout/1000);
	for (i=0; i<ngot; i++) {
		myfree(got[i]);
	}
        return bf;
}
void chan_setisihave(int c, int x)
{
	if (c<0) return;
	if (c>=MAX_CHAN) return;
	if (!chan_xisihave[c]) if (x) {
		x = atoi(config_get(C_IHAVE_CHAN_TIMEOUT));
		if (x>0) chan_settimeout(c,x);
	}
	chan_xisihave[c] = x;
}
int chan_isihave(int c)
{
	if (c<0) return FALSE;
	return chan_xisihave[c];
}
static int xn[MAX_CHAN];
void chan_init(int c)
{
        int tout = atoi(config_get(C_TIMEOUT))*60;
        if (tout==0) tout = 60*60;
        
        check(c);
	xn[c] = 0;
	chan_curdom[c] = NULL;
	chan_fn[c] = NULL;
	chan_reject[c] = FALSE;
	chan_isauser[c] = FALSE;
	chan_isaccess[c] = TRUE;
	chan_xisihave[c] = FALSE;
	chan_xisready[c] = FALSE;
        chan_grp[c] = -1;
	chan_isinbody[c] = FALSE;
        chan_num[c] = -1;
        chan_nart[c] = 0;
        chan_npost[c] = 0;
        chan_xinspeed[c] = 0;
        chan_xoutspeed[c] = 0;
	chan_ndup[c] = 0;
        chan_nreject[c] = 0;
        chan_size[c] = 0;
	chan_hash[c] = abs(rand()%10000000);
        chan_time[c] = lib_gmtime();
        chan_lasttime[c] = lib_gmtime();
        chan_tout[c] = tout;
        chan_state[c] = C_CMD;
	chan_dolog[c] = FALSE;
	user_starttime(c);
	cstat_init(c);

	if (chan_user[c]!=NULL) myfree(chan_user[c]);
	if (chan_pass[c]!=NULL) myfree(chan_pass[c]);
	chan_user[c] = NULL;
	chan_pass[c] = NULL;
	ihavein_clear(c);
	/* chan_isused[c] = TRUE; Shouldn't be here, else will record incoming ones 
	as used but never free them */
}
int chan_gethash(int c)
{
	return chan_hash[c];
}
int chan_isfree(int c)
{
 	return !chan_isused[c];
}
int chan_needauth(int c)
{
 	return !chan_isauth[c];
}
void chan_setauth(int c, int x)
{
 	chan_isauth[c] = x;
}
int chan_otheruser(int x)
{
	int c;
        for (c=0; c<nchan; c++) { /* change to 60*60 */
                if (chan_isopen(c)) {
		 if (strcmp(chan_getuser(c),chan_getuser(x))==0) {
			if (strcmp(chan_getipnum(c),chan_getipnum(x))!=0) {
				imsg("User already connected on channel %d \n",c);
				chan_isauth[c] = FALSE;
				return TRUE;	
			}
		 }
		}
	}
        return FALSE;
}
void chan_purge2(void)
{
        int c;
        int t = lib_gmtime();

        for (c=0; c<nchan; c++) { /* change to 60*60 */
          if (chan_isopen(c)) {
		if (nntp_getpending(c)) {
			ncmd_timeout(c);
		}
	  }
	}
}
void chan_purge(void)
{
        int c;
        int t = lib_gmtime();

        for (c=0; c<nchan; c++) { /* change to 60*60 */
          if (chan_isopen(c)) {
		if (nntp_getpending(c)) {
			ncmd_timeout(c);
		}
			
			chan_stats_add(c);
                  if ((t-chan_lasttime[c]) > (chan_tout[c]+10)) {
                        wmsg("chan: Forcing closed idle channel %d, idle for %d seconds, limit=%d\n"
                                ,c,t-chan_lasttime[c],chan_tout[c]);
                        nntp_doclose(c);
                  } else if ((t-chan_lasttime[c]) > chan_tout[c]) {
                        wmsg("chan: Closing idle channel %d, idle for %d seconds limit %d\n"
                                ,c,t-chan_lasttime[c],chan_tout[c]);
                        chan_close(c);
                  }
                }
        }

}
int chan_getgrp(int c)
{
        check(c);
        return chan_grp[c];
}
void chan_gotarticle(int c)
{
        check(c);
        chan_nart[c]++;
}
void chan_gotdup(int c)
{
	chan_ndup[c]++;
}
void chan_gotpost(int c, int ok, int size)
{
        check(c);
        chan_npost[c]++;
	if (!ok) chan_nreject[c]++;
	chan_size[c] += size;
}
void chan_setgrp(int c, int g)
{
        check(c);
        chan_grp[c] = g;
}
int chan_getnum(int c)
{
        check(c);
        return chan_num[c];
}
void chan_setnum(int c, int g)
{
        check(c);
        chan_num[c] = g;
}
int chan_canpost(int c)
{
        check(c);
        return chan_canpostit[c];
}
int chan_cantalk(int c)
{
	int r;
        check(c);
        if (chan_canpostit[c] || chan_canreadit[c] || chan_canihaveit[c]) {
		if (config_true(C_AUTH_SPAWN_IP)) {
			chan_setuser(c,"(NULL)");
			r = chan_check( c, "(NULL)");
			return r;
		}
		return TRUE;
	}
        else return FALSE;
}
int chan_canihave(int c)
{
        check(c);
        return chan_canihaveit[c];
}
int chan_ihaveonly(int c)
{
        check(c);
        if (!chan_canpostit[c] && !chan_canreadit[c] && chan_canihaveit[c]) return TRUE;
	return FALSE;
}
int chan_canreadgrp(int c, char *grp)
{
        check(c);
	if (config_true(C_GROUP_DLL)) {
		return dll_group(chan_getuser(c),grp,1);
	}
        if (group_match(chan_readgrps[c],grp)) return chan_canreadit[c];
        else return FALSE;
}
int chan_canpostgrp(int c, char *grp)
{
        check(c);
	if (config_true(C_GROUP_DLL)) {
		return dll_group(chan_getuser(c),grp,2);
	}
        if (group_match(chan_postgrps[c],grp)) return chan_canpostit[c];
        else return FALSE;
}
void chan_setuser(int c, char *u)
{
        check(c);
        if (chan_user[c] != NULL) myfree(chan_user[c]);
        chan_user[c] = mystrdup(u);
  	if (!config_true(C_AUTH_CASE)) strlwr(chan_user[c]);
        if (chan_user[c] == NULL) emsg("fatal: setuser, memory exceeded \n");
}
void chan_setpass(int c, char *u)
{
        check(c);
        if (chan_pass[c] != NULL) myfree(chan_pass[c]);
        chan_pass[c] = mystrdup(u);
}
char *chan_getid(int c)
{
	if (c<0) return "Unknown";
 	if (chan_ident[c]==NULL) return "Unknown";
	else return chan_ident[c];
}
char *chan_getipnum(int c)
{
	if (chan_ipnum[c]==NULL) return "panicipnum";
	return chan_ipnum[c];
}

int chan_nsim(char *ipnum);
int chan_nsim(char *ipnum)
{
 	int i;
	int n=0;
	for (i=0; i<nchan; i++) {
        if (chan_isopen(i)) {
			if (chan_ipnum[i]!=NULL) if (strcmp(ipnum,chan_ipnum[i])==0) n++;
        }
	}
	return n;
}
static char thisarea[BFSZ];
int chan_narea(char *ipwild);
int chan_narea(char *ipwild)
{
 	int i;
	int n=0;

	for (i=0; i<nchan; i++) {
          if (chan_isopen(i)) {
         	if (chan_ipnum[i]!=NULL) if (ip_match(ipwild,chan_ipname[i]) || group_match(ipwild,chan_ipnum[i])) {
			 n++;
		}
          }
	}
	return n;
}
int chan_narea_name(char *ipwild)
{
 	int i;
	int n=0;

	for (i=0; i<nchan; i++) {
          if (chan_isopen(i)) {
		if (strcmp(chan_getarea(i),ipwild)==0) {
			 n++;
		}
          }
	}
	return n;
}

int chan_nloggedin(char *username)
{
 	int c;
	int n=0;

	for (c=0; c<nchan; c++) {
          if (chan_isopen(c)) {
		if (chan_user[c]!=NULL) if (strcmp(username,chan_user[c])==0) n++;
          }
	}
	return n;
}

char *chan_getipname(int c)
{
        check(c);
	if (chan_ipname[c]==NULL) return "NULL_ipname";
        return chan_ipname[c];
}
/* setipnum must have been called first */
static int inspeed;
static int outspeed;
static int max_mb,max_users,max_con;
void chan_setipname(int c, char *s)
{
	char ipname[BFSZ],ipnum[BFSZ];
        char reason[BFSZ];
	int r;
	char *u;
	check(c);
        if (chan_ipname[c] != NULL) myfree(chan_ipname[c]);
        if (chan_postgrps[c]==NULL) chan_postgrps[c] = (char *) mymalloc(BFSZ2);
        if (chan_readgrps[c]==NULL) chan_readgrps[c] = (char *) mymalloc(BFSZ2);
        if (chan_readgrps[c]==NULL) emsg("chan: Memory  allocation failure\n");
        dmsg("chan: access check, setipname {%s}\n",s);
        chan_ipname[c] = mystrdup(s);
        strlwr(chan_ipname[c]);
	inspeed = 0;
	outspeed = 0;


	ncpy(ipname,chan_ipname[c],200);
	ncpy(ipnum,chan_ipnum[c],200);
	if (config_true(C_AUTH_VIRTUAL_IP)) {
		strcat(ipname,"@"); strcat(ipname,nntp_localip(c));
		strcat(ipnum,"@"); strcat(ipnum,nntp_localip(c));
		imsg("auth_virtual true, so ipname = (%s)  ipnum = (%s) \n",ipname,ipnum);
	}

        access_check(ipname,ipnum,"","",
                chan_postgrps[c],chan_readgrps[c],
                &chan_canpostit[c],&chan_canreadit[c],&chan_canihaveit[c],&chan_isauth[c]);
        dmsg("chan: {%s} isauth %d canihave %d canread %d {%s}  canwrite %d {%s} \n",
                ipname,chan_isauth[c],
                chan_canihaveit[c],
                chan_canreadit[c],chan_readgrps[c],chan_canpostit[c],
                chan_postgrps[c]);
	if (inspeed!=0) {
		imsg("Channel inspeed set to %d\n",inspeed);
	}
	if (outspeed!=0) {
		imsg("Channel outspeed set to %d\n",outspeed);
	}
	chan_xinspeed[c] = inspeed;
	chan_xoutspeed[c] = outspeed;
	chan_setarea(c,thisarea);
	if (!config_true(C_ML_DISABLE)) {
		imsg("max_mb = %d, max_con=%d, max_users=%d\n",max_mb,max_con,max_users);
		if (strcmp(ipnum,"127.0.0.1")==0) u = chan_ipnum[c]; else u=chan_ipname[c];
		if (strlen(u)==0) u = chan_ipnum[c];
		if (strlen(thisarea)==0) strcpy(thisarea,"(noarea)");
		r = ml_login(c,u,thisarea,max_con,max_users,max_mb*10000,reason);
		if (!r) {
			imsg("Limit (%s) set ml_disable true to disable these limits\n",reason);
			strcpy(elimit,reason);
			chan_canreadit[c] = FALSE;
			chan_canpostit[c] = FALSE;
			chan_canihaveit[c] = FALSE;
		}
	}
}
int chan_setinspeed(int c, int x)
{
	chan_xinspeed[c] = x;
	return TRUE;
}
int chan_inspeed(int c)
{
	static int x;
	static int done;
	static int max_speed;
	if (chan_xinspeed[c]!=0) {
		return chan_xinspeed[c];
	}
	max_speed = atoi(config_get(C_MAX_SPEED));
	if (max_speed==20000) {
		if (!done) imsg("Ignoring max_speed setting, use anything other than 20000 if you want to limit the speed\n");
		done = TRUE;
		max_speed = 10000000;
	}
	if (max_speed==0) max_speed = 100000000;
	return max_speed;
}	
static int omax_speed;
int chan_outspeed(int c)
{
	static int x;
	static int done;
	if (chan_xoutspeed[c]!=0) {
		return chan_xoutspeed[c];
	}
	if (omax_speed==0) omax_speed = atoi(config_get(C_MAX_OUT));
	if (omax_speed==0) omax_speed = 100000000;
	return omax_speed;
}	
void chan_setipnum(int c, char *s)
{
        check(c);
	omax_speed = 0;
        if (chan_ipnum[c] != NULL) myfree(chan_ipnum[c]);
        chan_ipnum[c] = mystrdup(s);
}
void chan_setident(int c, char *s)
{
        check(c);
        if (chan_ident[c] != NULL) myfree(chan_ident[c]);
        chan_ident[c] = mystrdup(s);
}
char *chan_getuser(int c)
{
        if (chan_user[c] == NULL) return "";
        return chan_user[c];
}
char *chan_getpass(int c)
{
        if (chan_pass[c] == NULL) return "";
        return chan_pass[c];
}
static int uoutspeed;
static char c_reason[BFSZ];
char *chan_reason(void)
{
	return c_reason;
}
int chan_check(int c, char *p)
{
	char ipname[BFSZ],user[BFSZ],ipnum[BFSZ];
	int r;
	char *u;
  	if (!config_true(C_AUTH_CASE)) strlwr(p);
	uoutspeed = 0;
	outspeed = 0;
	strcpy(c_reason,"");
	strcpy(elimit,"");
	ncpy(ipname,chan_ipname[c],200);
	ncpy(ipnum,chan_ipnum[c],200);
	ncpy(user,chan_user[c],200);
	if (config_true(C_AUTH_VIRTUAL_IP)) {
		strcat(ipname,"@"); strcat(ipname,nntp_localip(c));
		strcat(ipnum,"@"); strcat(ipnum,nntp_localip(c));
		if (config_true(C_AUTH_VIRTUAL_USER)) {
			if (strlen(user)>0) {strcat(user,"@"); strcat(user,nntp_localip(c));}
		}
		imsg("auth_virtual true, so user = (%s) ipname = (%s)  ipnum = (%s) \n",user,ipname,ipnum);

	}

	if (config_true(C_AUTH_DLL)) {
	  dll_auth(ipname,ipnum,user,p
		  ,chan_postgrps[c],chan_readgrps[c]
		  ,&chan_isauth[c]);
	  if (chan_isauth[c]) return TRUE;
	  return FALSE;
	}
        


	access_check(ipname,ipnum,user,p,
                chan_postgrps[c],chan_readgrps[c],
                &chan_canpostit[c],&chan_canreadit[c],&chan_canihaveit[c],&chan_isauth[c]);

  
	dmsg("user: canihave %d canread %d {%s}  canwrite %d {%s} isauth=%d \n",
                chan_canihaveit[c],
                chan_canreadit[c],chan_readgrps[c],chan_canpostit[c],
                chan_postgrps[c],chan_isauth[c]);

	dmsg("user: eilmit (%s)\n",elimit);
	strcpy(c_reason,elimit);
	if (!chan_isauth[c]) return FALSE;

	if (!config_true(C_ML_DISABLE)) {
		imsg("LOGGEDIN: max_mb = %d, max_con=%d, max_users=%d\n",max_mb,max_con,max_users);
		if (strcmp(ipnum,"127.0.0.1")==0) u = chan_ipnum[c]; else u=chan_ipname[c];
		if (strlen(u)==0) u = chan_ipnum[c];
		if (strlen(thisarea)==0) strcpy(thisarea,"(noarea)");
		r = ml_login(c,u,user,max_con,max_users,max_mb*10000,c_reason);
		if (!r) {
			wmsg("Exceeded: %s\n",c_reason);
			chan_isauth[c] = FALSE;
			return FALSE;
		}
	}


	if (chan_canpostit[c] || chan_canreadit[c]) {
		if (uoutspeed>0) {
			imsg("Channel-user outspeed set to %d\n",uoutspeed);
			chan_xoutspeed[c] = uoutspeed;
		}
		return TRUE;
	}
        return FALSE;
}
/* Split input on CRLF as that is defined in NNTP protocol 
   and makes everything else simpler 

        set an absolute maximum limit of 30,000K for a single line
in a news item, most news systems do not allow intire items more than
64K so this seems like a reasonable restriction.  (to me anyway).

The manual sais to split lines on CRLF, but INN accepts LF by itself so
we better too or it will break software.


*/
#ifdef NETWARE
#define MAXREC 12000
#else
#define MAXREC 30000
#endif

#define INSIZE MAXREC
#define LF "\012"
static char *xbuf[MAX_CHAN]; /* Increase to 8001 when debuging finished*/ 

static int in_size;
int chan_isreject(int c)
{
	return chan_reject[c];
}
void chan_setreject(int c, int t)
{
	chan_reject[c] = t;
}
void chan_record_junk(char *bf, int len)
{
	FILE *f;
	f = fopen(file_work("junkrecord.dat"),"wb");
	if (f==NULL) return;
	fwrite(bf,1,len,f);
	fclose(f);
	emsg("Bad record stored as %s\n",file_work("junkrecord.dat"));
}

void channel_read(int c)
{
        int nn;
	char *s2;
        static int want;
	int x_speed = atoi(config_get(C_MAX_SPEED));
	vtrace();
	if (in_size==0) in_size = atoi(config_get(C_IN_SIZE));
	if (in_size>20000) in_size = MAXREC-100;
	if (in_size==0) in_size = MAXREC-100;
	if (x_speed>0) if (x_speed<in_size) in_size = x_speed+100;
        if (xbuf[c]==NULL) xbuf[c] = (char *) mymalloc(INSIZE);
xretry:
        want = INSIZE-4-xn[c];
        if (want>in_size) want = in_size; /* Limit the size of chunks we process */
        if (want<=0) {
		*(xbuf[c]+xn[c]) = 0;
		s2 = strchr(xbuf[c],'\n');
		if (s2!=NULL) {
			imsg("chan: minor bug in processing data\n");	
			goto cont;
		}
                emsg("chan: Record too big in item, throwing it away, tough\n");
		chan_record_junk(xbuf[c],xn[c]);
		if (strchr(xbuf[c],'\r')!=NULL) 
                   emsg("chan: Found CR !! Record too big in item, throwing it away, tough\n");
		chan_reject[c] = TRUE;
                xn[c] = 0;
		goto xretry;
        }
        chan_lasttime[c] = xtime_now;
	stat_start(X_NNTP_READBF);
        nn = nntp_read(c, xbuf[c]+xn[c],want);
        if (nn>=0) gstat_xadd("Total In bytes/s",nn); /* brian */
	stat_end(X_NNTP_READBF);
        if (nn==0) wmsg("fatal: Read zero bytes, asked for %d bytes \n",want);
        if (nn>0) xn[c] +=nn;
        if (nn<0) {chan_close2(c); xn[c] = 0;}
cont:;
	stat_start(X_DOREST);
	if (xn[c] > 0) chan_dorest(c);

	stat_end(X_DOREST);
	vtrace();
}
int chan_ismore(int c)
{
 	return chan_more[c];
}
void chan_dorest(int c)
{
	static char rec[MAXREC];
	int rlen;
	char cc,b;
        char *s1,*s2,*s3,*s4;
	int start,k;
	int inbody;
	char save;
        if (xn[c]<=0) return; /* Nothing to process */

	vtrace();
	inbody = chan_inbody(c);
	start = lib_gmtime();
        s1 = xbuf[c];
        s1[xn[c]] = 0;   

	for (s4=s1;TRUE;s4++) {
		b = *s4;
		if (b==0) return;
		if (b=='\n') break;
		if (b=='\r') {
			if (s4[1]==0) return;
			if (s4[1]=='\n') {s4++; break;}
			break;
		}
	}
	s2 = s4;

	for (k=1;s2!=NULL;k++) { /*loop*/
        	rlen = s2-s1+1;
		/* If getting body, and this line doesnt 
		start '.'  and rec isn't too big and 
		next line is not a dot, then

		*/
		if (inbody) {
			if (rlen<2) goto donormal;
			if (*s2=='\r') goto donormal;
		}

		if (inbody) if (s1[0]!='.') if ((s2-s1)<(MAXREC-100)) if (s2>s1) if (s2[-1]=='\r') if (s2[1]!='.') {
again:
		/* If this is normal end of lien, and next line
		doesn't start with a dot */
			if (s2[-1]=='\r') if (s2[1]!='.') {
		/* Search for end of next line */
				for (s3=s2+1;*s3!=0;s3++) {
					b = *s3;
					if (b=='\n') break;
					if (b=='\r') {
						if (s3[1]=='\n') {
							s3++;
							break;
						} else {
							inbody = FALSE;
							goto dochunk;
						}
					}
				}
				if ((s3-s2)<(MAXREC-100)) if (*s3=='\n') {
					s2 = s3;
					goto again;
				}
			} else inbody = FALSE;
		/* Send chunk without copying it */
dochunk:		stat_start(X_CHREAD);
	        	rlen = s2-s1+1;
			save = s1[rlen];
			s1[rlen] = 0;
/*
ncpy(rec,s1,50);
emsg("chan_chunk %d bytes (%s)\n",rlen,rec);
*/

			chan_read(c,s1,rlen);
			s1[rlen] = save;
			stat_end(X_CHREAD);
			xn[c] -= rlen;
			goto endline;
		}	
		
donormal:		
		
		if (rlen>=(MAXREC-100)) {
                      emsg("Record too big %d \n",rec);
                      rlen = MAXREC-100;
        	}
		cc = s1[rlen];
		s1[rlen] = 0;
		strcpy(rec,s1); 
		s1[rlen] = cc;
/*		dmsg("chanrec: Got rec xn[%d]=%d (%d) \n",c,xn[c],rlen);*/
		xn[c] -= rlen;
		if (rlen>1) if (rec[rlen-2]!='\r') { /* add CR if missing */
		        strcpy(rec+rlen-1,EOL); rlen++;
		}
		if (rlen==1) if (rec[rlen-1]=='\n') { /* add CR if missing */
		        strcpy(rec+rlen-1,EOL); rlen++;
		}
		if (rlen==1) if (rec[rlen-1]=='\r') { /* add CR if missing */
		        strcpy(rec+rlen-1,EOL); rlen++;
		}
		stat_start(X_CHREAD);

		chan_read(c,rec,rlen);
		stat_end(X_CHREAD);
endline:
		inbody = chan_inbody(c);
		s1 = s2 + 1;
		/*for (s2=s1;*s2!=0;s2++) if (*s2=='\n') break;*/
		for (s2=s1;TRUE;s2++) {
			b = *s2;
			if (b==0) break;
			if (b=='\r') {
				if (s2[1]=='\n') {s2++; break;}
				break;
			}
			if (b=='\n') break;
		}
		if (*s2==0) s2 = NULL;
		if ((k%1000) == 999) if ((lib_gmtime()-start)>1) goto stopit;
		if (nntp_getbusy(c) || nntp_getpending(c) || nntp_outsz(c)>100000)  {
stopit:;		/* --  Then must leave the rest until next time. -- */
			chan_more[c] = FALSE;
			if (s2!=NULL) chan_more[c] = TRUE;

			break;
		}
	}
        memmove(xbuf[c],s1,strlen(s1)+1); 
}
void chan_dorest_xxx(int c)
{
	static char rec[MAXREC];
	int rlen;
	char cc;
        char *s1,*s2,*s3;
	int start,k;
	int inbody;
	char save;
        if (xn[c]<=0) return; /* Nothing to process */

	vtrace();
	inbody = chan_inbody(c);
	start = lib_gmtime();
        s1 = xbuf[c];
        s1[xn[c]] = 0;   
        s2 = mystrchr(s1,'\n');
	if (s2==NULL) return;
	
	for (k=1;s2!=NULL;k++) { /*loop*/
        	rlen = s2-s1+1;
		if (inbody) if (s1[0]!='.') if ((s2-s1)<(MAXREC-100)) if (s2>s1) if (s2[-1]=='\r') if (s2[1]!='.') {
again:
			if (s2[-1]=='\r') if (s2[1]!='.') {
				/* Search for end of next line */
				for (s3=s2+1;*s3!=0;s3++) if (*s3=='\n') break;
				if ((s3-s2)<(MAXREC-100)) if (*s3=='\n') {
					s2 = s3;
					goto again;
				}

			} else inbody = FALSE;
			/* Send chunk without copying it */
			stat_start(X_CHREAD);
	        	rlen = s2-s1+1;
			save = s1[rlen];
			s1[rlen] = 0;
/*
ncpy(rec,s1,50);
emsg("chan_chunk %d bytes (%s)\n",rlen,rec);
*/
			chan_read(c,s1,rlen);
			s1[rlen] = save;
			stat_end(X_CHREAD);
			xn[c] -= rlen;
			goto endline;
		}	
		
		
		
		if (rlen>=(MAXREC-100)) {
                      emsg("Record too big %d \n",rec);
                      rlen = MAXREC-100;
        	}
		cc = s1[rlen];
		s1[rlen] = 0;
		strcpy(rec,s1); 
		s1[rlen] = cc;
/*		dmsg("chanrec: Got rec xn[%d]=%d (%d) \n",c,xn[c],rlen);*/
		xn[c] -= rlen;
		if (rlen>1) if (rec[rlen-2]!='\r') { /* add CR if missing */
		        strcpy(rec+rlen-1,EOL); rlen++;
		}
		if (rlen==1) if (rec[rlen-1]=='\n') { /* add CR if missing */
		        strcpy(rec+rlen-1,EOL); rlen++;
		}
		stat_start(X_CHREAD);

		chan_read(c,rec,rlen);
		stat_end(X_CHREAD);
endline:
		inbody = chan_inbody(c);
		s1 = s2 + 1;
		for (s2=s1;*s2!=0;s2++) if (*s2=='\n') break;
		if (*s2==0) s2 = NULL;
		if ((k%1000) == 999) if ((lib_gmtime()-start)>1) goto stopit;
		if (nntp_getbusy(c) || nntp_getpending(c) || nntp_outsz(c)>100000)  {
stopit:;		/* --  Then must leave the rest until next time. -- */
			chan_more[c] = FALSE;
			if (s2!=NULL) chan_more[c] = TRUE;

			break;
		}
	}
        memmove(xbuf[c],s1,strlen(s1)+1); 
}
void chan_dorest_old(int c)
{
	static char rec[MAXREC];
	int rlen;
	char cc;
        char *s1,*s2;
	int start,k;
	int inbody;
        if (xn[c]<=0) return; /* Nothing to process */
	dmsg("chan: dorest len %d\n",xn[c]);
	vtrace();

	inbody = chan_inbody(c);
	start = lib_gmtime();
        s1 = xbuf[c];
        s1[xn[c]] = 0;   
 if (xn[c]<50) dmsg("chanxxx: dorest {%s} \n",s1); 

        s2 = mystrchr(s1,'\n');
        if (s2==NULL) return;
        for (k=1;s2!=NULL;k++) { /*loop*/
        	rlen = s2-s1+1;
         	if (rlen>=(MAXREC-100)) {
                      emsg("Record too big %d \n",rec);
                      rlen = MAXREC-100;
        	}
		cc = s1[rlen];
		s1[rlen] = 0;
		strcpy(rec,s1); 
		s1[rlen] = cc;
/*		dmsg("chanrec: Got rec xn[%d]=%d (%d) \n",c,xn[c],rlen);*/
		xn[c] -= rlen;
		if (rlen>1) if (rec[rlen-2]!='\r') { /* add CR if missing */
		        strcpy(rec+rlen-1,EOL); rlen++;
		}
		if (rlen==1) if (rec[rlen-1]=='\n') { /* add CR if missing */
		        strcpy(rec+rlen-1,EOL); rlen++;
		}
		stat_start(X_CHREAD);
		chan_read(c,rec,rlen);
		stat_end(X_CHREAD);
		s1 = s2 + 1;
		for (s2=s1;*s2!=0;s2++) if (*s2=='\n') break;
		if (*s2==0) s2 = NULL;
		if ((k%1000) == 999) if ((lib_gmtime()-start)>1) goto stopit;
		if (nntp_getbusy(c) || nntp_getpending(c) || nntp_outsz(c)>100000)  {
stopit:;		/* --  Then must leave the rest until next time. -- */
			chan_more[c] = FALSE;
			if (s2!=NULL) chan_more[c] = TRUE;
			break;
		}
	}
        memmove(xbuf[c],s1,strlen(s1)+1); 
}
void chan_setfn(int c, int (*fn)(int chan, char *data, int sz))
{
	chan_fn[c] = fn;
	chan_setstate(c,C_FN);
}
void chan_setstate(int c, int st)
{
        chan_state[c] = st;
}
void chan_newchan(int c)
{
 	if (config_true(C_PIDENT)) {
		if (atoi(config_get(C_SLAVE_N))>0) return;
		id_ask(c);
	}
}
int chan_setinbody(int c,int x)
{
	if (c<0) return TRUE;
	chan_isinbody[c] = x;
	return TRUE;
}
int chan_inbody(int c)
{
	int x = chan_state[c];	
	if (chan_isinbody[c]) return TRUE;
	if (x==C_IHAVE) return TRUE;
	return FALSE;

}
int chan_read(int c, char *ss, int sz)
{
        int r=0;
        char bf[BFSZ];
	if (chan_dolog[c]) {
	 	chan_writelog(c,TRUE,ss,sz);
	}
	cstat_addin(c,sz);
	chan_xisready[c] = TRUE;
        if (debug_lvl>=D_DEBUG) {
                ncpy(bf,ss,199);
                dmsg("chan_read: %d:%d, %s",sz,strlen(bf),bf);
        }
        switch (chan_state[c]) {
          case C_CMD:
		vtrace();
                r = do_cmd(c,ss,sz);
		vtrace();
                break; 
          case C_WAIT:
		vtrace();
                /*dmsg("wait, DATA LOST: %s",ss); */
                break;
          case C_IHAVE:
		stat_start(X_IDATA);
                r = ncmd_getihave(c,ss,sz);
		stat_end(X_IDATA);
                break;
          case C_LISTGROUP:
                r = suck_listgroup(ss,sz);
                break;
          case C_POST:
		vtrace();
                r = ncmd_getpost(c,ss,sz);
                break;
          case C_CHECK:
		vtrace();
                if (debug_lvl>=D_DEBUG) if (strlen(ss)<1000) dmsg("chan: check %s",ss);
                r = suck_check(ss,sz);
                break;
          case C_GETITEM:
                r = suck_getitem(ss,sz);
                break;
          case C_GETITEM_STATUS:
                r = suck_getitem_status(ss,sz);
                break;
          case C_GETITEM_NEXT:
                r = suck_getitem_next(ss,sz);
                break;
          case C_GETLIST:
		vtrace();
                r = suck_getlist(ss,sz);
                break;
          case C_STREAM_ITEM:
                r = suck_stream_item(ss,sz);
                break;
          case C_LIVE:
                live_read(c,ss,sz);
                break;
          case C_STREAM_NEXT:
                r = suck_stream_next(ss,sz);
                break;
          case C_STREAM_GROUP:
		vtrace();
                r = suck_stream_group(ss,sz);
                break;
          case C_ID_READ:
		vtrace();
                id_read(c,ss,sz);
                break;
          case C_POP_READ:
		vtrace();
                pop_read(c,ss,sz);
                break;
	  case C_CLOSED:
		vtrace();
	  	imsg("Data from channel that is closed [%d] %s\n",c,str_trunc(ss,sz));
	  	break;
	  case C_FN:
		vtrace();
		/*dmsg("chan_read: fn=%p %.80s \n",chan_fn[c],ss);*/
		  if (chan_fn[c]!=NULL) (*chan_fn[c])(c,ss,sz);

        }
        return r;
}

/* NEXT ADD ITEM_BODY CALL*/
void test_read(void)
{
        int g;
        String *head, *body;

        g = group_find("rec.humor.funny");
        head = item_head(g,4243);
        body = item_body(g,4243);
        
        dmsg("cmd: Result {%s} \n",str_s(head));
        dmsg("cmd: body {%s} \n",str_s(body));
}
int do_cmd(int chan, char *in, int sz)
{
        char *aa;
        
        aa = strstr(in,"\015");
        if (aa==NULL) {dmsg("cmd: Waiting for EOL \n"); return 0;}
        *aa = 0;
        do_ncmd(chan,in);
        return TRUE;
}
/*----------------------------------------------------------------------*/
static String *bufstr;
void chan_write_buff(int c, char *s)  /*cannot take whole articles yet*/
{
	if (config_true(C_CHAN_NOBUFF)) {
	 	chan_write_s(c,s);
		return;
	}
        if (bufstr==NULL) bufstr = str_newsz(4000);
	if (*s==0) return;
	if (str_len(bufstr)>max_strbf()) {
		emsg("FATAL, 5mb data bufferred on this channel, loosing data [%d]\n",c);
		return;
	}
        str_addsn(bufstr,s);
}
void chan_write_buff_str(int c, String *s)  /*cannot take whole articles yet*/
{
	if (config_true(C_CHAN_NOBUFF)) {
	 	chan_write_str(c,s);
		return;
	}
        if (bufstr==NULL) bufstr = str_newsz(4000);
        str_add(bufstr,s);
	if (str_len(bufstr)>(max_strbf())) {
		emsg("FATAL, chan_write buffer exceeded, dumping data\n");
		str_clear(bufstr);
	}
}
void chan_write_flush(int c)
{
        if (bufstr==NULL) bufstr = str_newsz(4000);
        if (str_len(bufstr)==0) return;
        chan_write(c,bufstr);
        str_clear(bufstr);
}
void chan_write_s(int c, char *s)
{
        static String *ss;
        if (strlen(s)==0) return;

        if (ss==NULL) ss = str_new();
        str_copysn(ss,s);
        chan_write(c,ss);
        str_clearbig(ss);
}
void chan_write_slen(int c, char *s,int len)
{
        static String *ss;

	if (config_true(C_NNTP_WRITE_FAST)) {
		if (len==0) return;
		if (s[0]==0) return;
		nntp_write_fast(c,s,len);
		return;
	}
	
	if (strlen(s)==0) return;

        if (ss==NULL) ss = str_new();
        str_copys(ss,s,len);
        chan_write(c,ss);
        str_clearbig(ss);
}
void chan_write_str(int c, String *s)
{
        chan_write(c,s);
}
/* Split into 6K chunks, so as not to overload anything */
#define chunk 6000
void chan_write(int c, String *ss)
{
        char bf[BFSZ];
        if (!chan_isopen(c)) {imsg("Channel not open (%s)\n",nocrlf(str_s(ss))); return;}
	cstat_addout(c,str_len(ss));

	if (chan_dolog[c]) {
	 	chan_writelog(c,FALSE,str_s(ss),str_len(ss));
	}

        if (debug_lvl>=D_DEBUG) {
                ncpy(bf,str_s(ss),199);
                dmsg("chan_write: %s",bf);
        }
	if (ss!=NULL) user_addinfo(c,0,0,str_len(ss));
	gstat_xadd("Total Out bytes/s",str_len(ss));
        nntp_write(c,ss);
}
static FILE *flog;
void chan_savelog(int c)
{
	char bf[4000];
	char fname[BFSZ];
	char ename[BFSZ];
	FILE *f,*fout;
	int r;

	if (!chan_dolog[c]) return;
	if (flog!=NULL) fclose(flog);
	flog = NULL;

	sprintf(bf,"chan_%d.log",c); strcpy(fname,file_log(bf));
	sprintf(bf,"chan_%d.err",c); strcpy(ename,file_log(bf));
	emsg("Copying log of failure to %s\n",ename);
	f = fopen(fname,"r");
	if (f==NULL) return;
	fout = fopen(ename,"w");
	if (fout==NULL) {fclose(f); return;} 
	for (;!feof(f);) {
		r = fread(bf,1,1000,f);
		if (r<0) break;
		fwrite(bf,1,r,fout);
	}
	fclose(f);
	fclose(fout);
}

int chan_nchan(void)
{
	return nchan;
}
void chan_stats_add(int c)
{
	int i;
	if (chan_npost[c]<10) return;
	for (i=0; i<nchan; i++) {
	  if (i!=c) if (chan_isopen(i)) {
		  dmsg("chan: chan_stats_add %d %d\n",c,i);
	  	if (strcmp(chan_getipname(c),chan_getipname(i))==0) {
		  dmsg("chan: stats_feed %d %d\n",c,i);
			stats_feed(chan_getipname(i),0,chan_nreject[i],chan_npost[i]-chan_nreject[i],chan_ndup[i],chan_size[i]);
			chan_time[i] = xtime_now;
			chan_nreject[i] = 0;
			chan_npost[i] = 0;
			chan_ndup[i] = 0;
			chan_size[i] = 0;
		}
	  }
	}
	stats_feed(chan_getipname(c),xtime_now-chan_time[c],chan_nreject[c],chan_npost[c]-chan_nreject[c],chan_ndup[c],chan_size[c]);
	chan_time[c] = xtime_now;
	chan_nreject[c] = 0;
	chan_npost[c] = 0;
	chan_ndup[c] = 0;
	chan_size[c] = 0;
}
void hist_freechan(int c);
void chan_closing(int c)
{
	user_record(c);
	slave_closing(c);
	hist_freechan(c);

	if (!config_true(C_ML_DISABLE)) {
		ml_logout(c);
	}

	if (chan_fn[c]!=NULL) (*chan_fn[c])(c,"Channel Closing",-1);
}
void chan_close2(int c)
{
        double r;
	int diff;
        if (chan_npost[c]>0) {
		stats_feed(chan_getipname(c),lib_gmtime()-chan_time[c],chan_nreject[c],chan_npost[c]-chan_nreject[c],chan_ndup[c],chan_size[c]);
                diff = (lib_gmtime()-chan_time[c]);
		if (diff==0) r = 1;
                else r = (double) chan_npost[c] / (double) diff ;
                imsg("chan: Channel %d closed Total %d, failed %d, speed %g \n",c
                        ,chan_npost[c],chan_nreject[c],r);	
        }
        nntp_close(c);
	chan_state[c] = C_CLOSED;
	cstat_log(c);
	chan_setarea(c,"");
}
void chan_close(int c)
{
	chan_close2(c);
	chan_isused[c] = FALSE;

}
int chan_isopen(int c)
{
        return nntp_isopen(c);
}
/*----------------------------------------------------------------------*/
#define ACCESS file_config("access.conf")
static Sary *afile;
/*extern int stop_reading;*/
void access_convert(void);
void access_read(void)
{
	if (afile!=NULL) s_free(afile);

        afile = s_alloc();
        s_read_long(afile,ACCESS);
	if (!config_true(C_NODNS)) if (config_true(C_ACCESS_FORWARD)) {
		access_convert();
	}
/*
	for (i=0; i<s_len(afile); i++)  {
		imsg("New File (%s)\n",s_line(afile,i));
	}
*/
	in_size=0; /* Doesn't matter where as long as it happens every now and then */
}
void access_convert(void)
{
	int i,j,n;
	char ip[BFSZ];
	char bf[BFSZ2];
	char bf2[BFSZ2];
	char *s;
	Sary *snew;	
	char *p[200];
	static char *rslt[500];
	if (rslt[0]==NULL) {
		for (i=0; i<300; i++) rslt[i] = mymalloc(20);
	}
	snew = s_alloc();
	for (i=0; i<s_len(afile); i++)  {
		ncpy(bf,s_line(afile,i),BFSZ2-1);
		s = strchr(bf,'#'); if (s!=NULL) *s = 0;
                n = token_split2(bf,p,":",90);
		if (n<3) goto addit;
		if (strncmp(p[0],"#",1)==0) goto addit;
		strlwr(p[1]);
		if (strchr(p[1],'i')!=NULL) goto cont1;
		if ((strchr(p[1],'r')!=NULL) ||
			(strchr(p[1],'p')!=NULL)) {
			if (config_true(C_ACCESS_FORWARD_ALL)) goto cont1;
			else goto addit;
		}
		goto addit;

cont1:
		ncpy(ip,p[0],200);
		if (strlen(ip)==0) goto addit;
		if (isdigit(ip[0])) goto addit;
		if (strchr(ip,'*')!=NULL) goto addit;
		if (strchr(ip,',')!=NULL) goto addit;
		if (strchr(ip,'[')!=NULL) goto addit;
		n = nntp_lookup(ip,rslt,300);
		if (n==0) {
			imsg("Could not lookup name in access.conf (%s) disable lookup by setting 'access_forward false' \n", bf);
			goto addit;
		} else {
			ncpy(bf,s_line(afile,i),BFSZ2-1);
			s = strchr(bf,':');
			for (j=0; j<n; j++) {
				sprintf(bf2,"%s%s",rslt[j],s);
				s_add(snew,bf2);
				imsg("Converting access.conf name %s->%s\n",ip,rslt[j]);
			}
		}
		continue;
addit:
		ncpy(bf,s_line(afile,i),BFSZ2-1);
		dmsg("access: (%s) \n",bf);
		s_add(snew,bf);
	}
	s_free(afile);
	afile = snew;
}
/* ##   <host>:<perm>:<user>:<pass>:<groups> */

/*
*:logoff:::!*  
*.netwin.co:read,post:::!*
*.netwin.co:read,post:joe:secret:*,comp.*

*:netwin.co.nz:read,post:::*:con=3    (simultaneous connections from one ip number)
*:netwin.co.nz:read,post:::*:users=30 (simultaneous users from one ip range)

*.netwin.co.nz:read,post:$ntuser$:$ntpass$:*
*/
int access_lookup(char *user, char *pass);
char *chan_elimit(void)
{
 	return elimit;
}
void access_add(char *cur, char *add, int isadd, int isaddonly, int matched)
{
	char bf[BFSZ];
	char *s1,*s2,*s3;
	int len;
	int isnot;

	dmsg("access: access_add %s %s cur=%s add=%s addonly=%d\n",
		matched ? "matched" : "", isadd ? "isadd" : "", cur,add,isaddonly);
	if (isaddonly) if (matched) goto addit;
	if (!isadd) if (!matched) return;
	if (!isadd) strcpy(cur,"");

addit:
	s1 = add;
	for (;s1!=NULL;) {
		s2 = strchr(s1,',');
		if (s2==NULL) len = strlen(s1);
		else len = s2-s1;
		ncpy(bf,s1,len);
		if ((strlen(cur)+strlen(bf))>=(BFSZ2-10)) return;
		s3 = bf;
		isnot = FALSE;
		if (*s3=='!') {s3++; isnot = TRUE;}
		if (!matched)  isnot = !isnot;
		if (*cur!=0) strcat(cur,",");
		if (isnot) strcat(cur,"!");
		strcat(cur,s3);
		if (s2==NULL) break;
		s1 = s2+1;
	}
}
static int udone;
static int uok;
static char *ugroups[400];
static int nugroups;
static char uuser[BFSZ],upass[BFSZ];
void uclear(char *user, char *pass)
{
	int i;
	ncpy(uuser,user,BFSZ-1);
	ncpy(upass,pass,BFSZ-1);
	udone = FALSE;
	for (i=0; i<nugroups; i++) {
		if (ugroups[i]!=NULL) myfree(ugroups[i]);
		ugroups[i] = NULL;
	}
}
int user_doneauth(void);

int ulookup2(char *user, char *pass)
{
	if (user_doneauth()) if (strcmp(uuser,user)==0) return TRUE;
	if (strcmp(uuser,user)==0) if (strcmp(upass,pass)==0) {
		return TRUE;
	}
	if (config_true(C_AUTH_WILD)) {
		if (strcmp(user,"*")==0) return TRUE;
		if (strcmp(uuser,user)==0) if (strcmp(pass,"*")==0) return TRUE;
	}
	return FALSE;
}
int ulookup(void)
{
	int i;
	if (strlen(uuser)==0) return FALSE;
	if (udone) return uok;
	nugroups = 0;
	uok = user_lookup(uuser,upass,ugroups,&nugroups,400,FALSE);
	imsg("User lookup (%s) %s, %d groups \n",uuser,uok ? "OK" : "Failed",nugroups);
	if (uok) user_cache_add(uuser,upass,ugroups,nugroups);
	for (i=0; i<nugroups; i++) {
		if (i<4) imsg("chan: %s in usergroup [%d] %s\n",uuser,i,ugroups[i]);
		else dmsg("chan: %s in usergroup [%d] %s\n",uuser,i,ugroups[i]);
	}
	return uok;
}
int access_isamember(char *group);
int access_ismember(char *user, char *list)
{
	char *s;
	char bf[BFSZ];
	int isgroup = FALSE;
	ncpy(bf,list,BFSZ-1);
	s = strtok(bf,",");
	for (;s!=NULL;) {
		if (strncmpnc(s,"groups=",7)==0) {
			s += 7;
			isgroup = TRUE;
		}
		if (isgroup) if (access_isamember(s)) return TRUE;
		else if (strcmp(user,s)==0) return TRUE; 
		s = strtok(NULL,",");
	}
imsg("The user (%s) is not a member of any of (%s) \n",user,list);
	return FALSE;
}
int is_umember(char *user, char *list);
int is_umember(char *user, char *list)
{
	char *s;
	char bf[BFSZ];
	ncpy(bf,list,BFSZ-1);
	s = strtok(bf,",");
	for (;s!=NULL;) {
		if (strcmp(user,list)==0) return TRUE;
		s = strtok(NULL,",");
	}
	return FALSE;
}
int access_isamember(char *group)
{
	int i;
	for (i=0; i<nugroups; i++) {
		if (strcmpnc(group,ugroups[i])==0) return TRUE;
	}
	return FALSE;
}
int user_set_ipx(char *s);

int access_check(char *ipname, char *ipnum,char *ipuser, char *ippass
        , char *postgrps, char *readgrps, int *canpost, int *canread, int *canihave, int *isauth)
{
        int i;
        char *p[100];
        char user[BFSZ],pass[BFSZ],ipwild[BFSZ],grps[BFSZ2],bf[BFSZ2];
        int isread,ispost,isihave,islogoff;
	char options[BFSZ];
	char *op[20];
	int nop;
        char *s;
	/*int narea;*/
	int j;
        int n,xislogoff=FALSE;
	int nsim;
	int isadd,isaddonly;
	int iscase = FALSE;
	int linematch = FALSE;
	int l_max_con,l_max_mb,l_max_users;
	uclear(ipuser,ippass);
	if (config_true(C_AUTH_CASE)) iscase = TRUE;

/*	imsg("Access check for (%s) (%s) (%s) \n",ipname,ipnum,ipuser);*/

	nsim = chan_nsim(ipnum);
  	dmsg("chan: Simultaneous connections for %s %d\n",ipnum,nsim);
        *canread = FALSE;
        *canpost = FALSE;
        *canihave = FALSE;
	*isauth = FALSE;
        strcpy(postgrps,"*");   
        strcpy(readgrps,"*");   
	max_mb = max_users = max_con = 0;
	l_max_mb = l_max_users = l_max_con = 0;
	user_set_ipx(ipnum);
        if (afile==NULL) return TRUE; /* Not running dnews so don't check*/
                
	strcpy(thisarea,"");
        for (i=0; i<s_len(afile); i++) {      /*loop*/
		l_max_mb = l_max_users = l_max_con = 0;
		user_set_ipx(ipnum);
		isadd = FALSE;
		isaddonly = FALSE;
                ncpy(bf,s_line(afile,i),BFSZ2-1);
                if (!iscase) strlwr(bf);
		s = strchr(bf,'#'); if (s!=NULL) *s = 0;
		dmsg("access: check (%s) \n",bf);
                isihave = isread = ispost = islogoff = FALSE;
                n = token_split2(bf,p,":",90);
                if (n==0) continue;
                s = p[0];
                for (;(isspace(*s)) && (*s!=0);s++) ;
                ncpy(ipwild,s,BFSZ-1);
                if (strncmp(ipwild,"#",1)==0) continue;
                if ((n<4) || (n>6)) {emsg("Access: Invalid Line %d  \n",i); continue; }
		if (n==4) p[4] = "!*";
		if (iscase) strlwr(ipwild);
		if (iscase) strlwr(p[1]);
                if (strchr(p[1],'r')!=NULL) isread = TRUE;
                if (strchr(p[1],'p')!=NULL) ispost = TRUE;
                if (strchr(p[1],'i')!=NULL) isihave = TRUE;
                if (strchr(p[1],'l')!=NULL) islogoff = TRUE;
                ncpy(user,p[2],BFSZ-1);
                ncpy(pass,p[3],BFSZ-1);
                ncpy(grps,p[4],BFSZ2-1);
                str_trimspace(grps);
		if (iscase) strlwr(grps);      
                if (ip_match(ipwild,ipname) || ip_match(ipwild,ipnum)) {
			dmsg("access: Applying line %s\n",s_line(afile,i));
			dmsg("access: isauth is %d\n",*isauth);

			if ((strlen(ipuser)>0) && (strlen(user)==0) && (strlen(ippass)>0)) {
			} else {
				strcpy(elimit,"");
			}
			
			
			xislogoff = FALSE;
			/*strcpy(thisarea,"");*/
			if (n==6) {
				ncpy(options,p[5],BFSZ-1);
				strlwr(options);
				dmsg("chan: OPtions {%s} \n",options);
				nop = token_split(options,op,",",15);
				for (j=0; j<nop; j++) {
				  if (strncmp(op[j],"con=",4)==0) {
					l_max_con = atoi(op[j]+4);
/*					if (strcmp(ipnum,"127.0.0.1")!=0)
					  if (nsim>atoi(op[j]+4)) {
						sprintf(elimit,"Too many connections: %s %d, limit is %s",ipname,nsim,op[j]);
				 		xislogoff = TRUE;
					}
*/
				  }
				  if (strncmp(op[j],"speed=",6)==0) {
					inspeed = atoi(op[j]+6);
				  }
				  if (strncmp(op[j],"max_in=",7)==0) {
					inspeed = atoi(op[j]+7);
				  }
				  if (strncmp(op[j],"max_out=",8)==0) {
					outspeed = atoi(op[j]+8);
				  }
				  if (strncmp(op[j],"area=",5)==0) {
					ncpy(thisarea,op[j]+5,BFSZ-1);
				  }
				  if (strncmp(op[j],"mb=",3)==0) {
					l_max_mb = atoi(op[j]+3);
				  }
				  if (strncmp(op[j],"users=",6)==0) {
					l_max_users = atoi(op[j]+6);
/*					narea = chan_narea(ipwild);
					if (strlen(thisarea)>0) {
						narea = chan_narea_name(thisarea)+1;
					}
					imsg("chan: Currently %d users from %s %s\n",narea,ipwild,thisarea);
				 	if (narea>atoi(op[j]+6)) {
						if (strlen(thisarea)==0) 
						      sprintf(elimit,"Too many connections: %s %d, limit is %s",ipwild,narea,op[j]);
						else  sprintf(elimit,"Too many connections: %s %d, limit is %s",thisarea,narea,op[j]);
				 		xislogoff = TRUE;
					}
*/
				  }
				  if (strcmp(op[j],"add")==0) isadd=TRUE;
				  if (strcmp(op[j],"addonly")==0) isaddonly=TRUE;
				} 	
			}
			if (isadd) if (!*isauth) continue;  /* Ignore these lines*/
			if (isaddonly) if (!*isauth) continue;  /* Ignore these lines*/
			linematch = TRUE;

			if (strlen(ipuser)>0) if (strlen(user)==0) if (strlen(ippass)>0) {
				dmsg("access: Ignoring line as we are checking the password\n");
				continue; /* Ignore non username lines*/
			}
			if ((strlen(user)==0) && (strlen(pass)==0)) {
				if (strlen(ipuser)==0) *isauth = TRUE;	
			} else if (strcmpnc(user,"$lookup$")==0) { /* Authenticate user */
				linematch = FALSE;
				if (ulookup()) {
					if (strlen(ipuser)>0) {
						*isauth = TRUE;
						if (*isauth) uoutspeed = outspeed;
						linematch = TRUE;
					}
				} else {
					*isauth = FALSE;
				}
				dmsg("access: isauth = %d \n",*isauth);
			} else if ((strlen(pass)==0) && (strlen(user)>0)) { /* Add access to authenticated user */
				linematch = FALSE;
				if (ulookup()) {
					linematch = access_ismember(ipuser,user);
					if (!linematch) if (strlen(ipuser)>0) if (strcmp(user,ipuser)==0) linematch = TRUE;
				}
			} else { 
				if (strlen(user)>0) { /* Lookup old style user */
					if (!ulookup2(user,pass)) {
						linematch = FALSE;
						if (strlen(ipuser)>0) continue;
					} else imsg("Local lookup matched %d\n",linematch);
					*isauth = linematch;
					if (*isauth) uoutspeed = outspeed;
				} else {
					*isauth = TRUE;
					if (*isauth) uoutspeed = outspeed;
				}
			} /* Non user line */
	
			if (islogoff || xislogoff) {
				dmsg("access: islogoff %d xislogoff %d\n",islogoff,xislogoff);
				*canread = FALSE;
				*canpost = FALSE;
				*canihave = FALSE;
				*isauth = FALSE;
			} else {
                         if (isread) {
				access_add(readgrps,grps,isadd,isaddonly,linematch);
				*canread = TRUE;
                         }
                         if (ispost) {
				access_add(postgrps,grps,isadd,isaddonly,linematch);
				*canpost = TRUE;
                         }
                         if (isihave) *canihave = TRUE;
			 if (!ispost) if (isihave) if (config_true(C_IHAVE_ACCESS)) {
                                ncpy(postgrps,grps,BFSZ2-1); 
                                *canpost = TRUE;
			 }
			 if (linematch) {
				max_mb = l_max_mb;
				max_users = l_max_users; 
				max_con = l_max_con;
			 }
			}
                }

        }


	if (islogoff || xislogoff) {
		if (strlen(elimit)>0) wmsg("Exceeded: %s\n",elimit);
	}
	

        return TRUE;	     
}
#ifdef WIN32
#include <windows.h>
#endif
int access_lookup(char *xuser, char *pass)
{
#ifdef WIN32
	HANDLE token;
	int r;
	char user[BFSZ];
	static char lastuser[BFSZ];
	static char lastpass[BFSZ];
	static int lastok,rr;
	int ltype;
	ncpy(user,xuser,BFSZ-1);
	if (strcmp(lastuser,user)==0) if (strcmp(lastpass,pass)==0) {
		return lastok;
	}
	
	ncpy(lastuser,user,BFSZ-1);
	ncpy(lastpass,pass,BFSZ-1);
	dmsg("xlogin: User (%s) pass (%s) \n",user,pass);
	ltype = LOGON32_LOGON_INTERACTIVE;
	if (config_true(C_NT_USER_BATCH)) {
		ltype = LOGON32_LOGON_BATCH;
	}

	r = LogonUser(user,config_get(C_NT_DOMAIN),pass,ltype,LOGON32_PROVIDER_DEFAULT,&token);
	if (r) imsg("User successfully logged in %s\n",user);
	else {
		rr = GetLastError();
		if (rr==ERROR_PRIVILEGE_NOT_HELD) imsg("Error, priv not held, you need to run dnews with 'may act as part of the system' priv\n");
		else imsg("Err %d User did not match %s\n",rr,user);
		imsg("For mixed case user/passwords set 'auth_case true' in dnews.conf\n");
	}
	lastok = r;
	CloseHandle(token);	
	return r;
#else
	return unix_lookup(xuser,pass);
#endif
} 
#ifdef WIN32
#undef WINVER
#define WINVER 0x401


#include <ras.h>
#endif
int chan_open_bind(char *s, char *ip)
{
	int r;
	if (ip!=NULL) nntp_set_bind(ip);
	r = chan_open(s);
	nntp_set_bind("");
	return r;
}
int chan_open(char *s)
{
        int r;
	char bf[BFSZ];
        int t=lib_gmtime();
        static char dont_try[BFSZ];
        static int dont_time;
        int elap,delay;

        imsg("chan: chan_open {%s} \n",s);
	if (config_true(C_FEED_TESTING)) {
	if (strncmp(s,"_bike",5)==0) {
		s = strchr(s+3,'_');
		if (s==NULL) return -1;
		strcpy(bf,s+1);
		s = bf;
		imsg("Converted host name to (%s)\n",s);
	}
	}
	ras_dial(s);
	if (strcmp(dont_try,s)==0) {
                if (lib_gmtime() < dont_time) {
                        emsg("chan: open won't retry {%s} for another %d minutes\n"
                                ,s,(dont_time-lib_gmtime())/60);
                        return -1;
	        }
	}

#ifdef WIN32
	if (config_true(C_AUTODIAL)) {	
	{
		RASAUTODIALENTRY entry;
		static HINSTANCE h;
		static FARPROC setauto;
		static char lastone[BFSZ];
		char xhost[BFSZ];
		char *s2;
		strcpy(xhost,s);
		s2 = strchr(xhost,':');
		if (s2!=NULL) *s2 = 0;
		
		entry.dwSize = sizeof(entry);
		entry.dwFlags = 0;
		entry.dwDialingLocation = 0;
		entry.szPhonebook[0] = 0;
		entry.szEntry[0] = 0;

		if (setauto==NULL) {
		 h = LoadLibrary("rasapi32");
		 if (h==NULL) {emsg("Cannot load rasapi32.dll %d\n",GetLastError()); goto skip;}
		 setauto = GetProcAddress(h,"RasSetAutodialAddressA");
		 if (setauto==NULL) {emsg("Could not find entry point: RasSetAutodialAddressA\n"); goto skip;}
		} 
		imsg("Setting autodial for (%s) \n",xhost);
		r = setauto(xhost,0,NULL,sizeof(entry),0);			
		r = setauto(xhost,0,&entry,sizeof(entry),1);			
	}
	}
skip:;
#endif

        r = nntp_open(s);
        elap = lib_gmtime()-t;
        if (elap > 4) emsg("chan: Channel open took a long time {%s} %d secs \n",s,elap);
        if (r<0) {
                delay = atoi(config_get(C_OPEN_RETRY));
		if (delay>0) {
	                emsg("chan: Site {%s} is down, won't retry for %d minutes\n",s,delay);
	                ncpy(dont_try,s,BFSZ-1);
	                dont_time = lib_gmtime() + delay * 60;
		}
	}
        dmsg("chan: chan_opened {%s} %d\n",s,r);
	if (r>=0) chan_isused[r] = TRUE;
        return r;
}
#ifdef WIN32
int dll_auth(char *ipname, char *ipnum, char *user, char *pass
	     ,char *postgrps, char *readgrps, int *isauth)
{
	return TRUE;
}
int dll_group(char *user, char *group, int type)
{
	return TRUE;
}
#else

#ifdef HAS_DLOPEN
#include  <dlfcn.h>
static int dl_init;
static void *dl_lib;
int dll_auth(char *ipname, char *ipnum, char *user, char *pass
	     ,char *postgrps, char *readgrps, int *isauth)
{
	static int (*auth_user)(char *ipname, char *ipnum, char *user, char *pass
	     ,char *postgrps, char *readgrps, char *msg, int bfsz);
	
	char msg[BFSZ];
	char *fn;
	strcpy(msg,"");
	*isauth = FALSE;
	if (!dl_init) {
		imsg("chan: Opening libauth.so\n");
		dl_lib = dlopen(file_config("libauth.so"),RTLD_LAZY);
		if (dl_lib==NULL) {filemsg("Could not open libauth.so:  %s\n",dlerror()); return FALSE;}
		dl_init = TRUE;
	}
	if (auth_user==NULL) {
		fn = "auth_user";
		auth_user = dlsym(dl_lib,fn);
		if (auth_user==NULL) {filemsg("Unable to find %s symbol %s\n",fn,dlerror()); return FALSE;}
		imsg("lib: Calling dll function to check user (%s)\n",user);
	}
	*isauth = (*auth_user)(ipname,ipnum,user,pass
		,postgrps,readgrps,msg,BFSZ2-1);
	if (strlen(msg)>0) imsg("auth_user: %s",msg);
	return TRUE;

}

static int dlx_init;
static void *dlx_lib;
int dnewsauth(char *user, char *pass, char *usergrps, int bfsz)
{
	static int (*dauth)(char *user, char *pass, char *usergrps, int bfsz, char *msg);
	char msg[BFSZ];
	int r;
	char libname[BFSZ];
	strcpy(msg,"");
	if (!dlx_init) {
		strcpy(libname,"dnewsauth.so");
		if (strlen(config_get(C_AUTH_LIB))>0) {
			strcpy(libname,config_get(C_AUTH_LIB));
		}
		imsg("chan: Opening %s\n",libname);
		dlx_lib = dlopen(libname,RTLD_LAZY);
		if (dlx_lib==NULL) {filemsg("Could not open %s:  %s\n",libname,dlerror()); return FALSE;}
		dlx_init = TRUE;
	}
	if (dauth==NULL) {
		dauth = dlsym(dlx_lib,"dnewsauth");
		if (dauth==NULL) {filemsg("Unable to find dnewsauth() symbol %s\n",dlerror()); return FALSE;}
		imsg("lib: Calling dll function to check user (%s)\n",user);
	}
	r = (*dauth)(user,pass,usergrps,bfsz,msg);
	if (strlen(msg)>0) imsg("dnewsauth: %s\n",msg);
	return r;
}
/* type 1=read, 2=post, 3=contro...? */
int dll_group(char *user, char *group, int type)
{
	static int (*auth_group)(char *user, char *group, int type, char *msg);
	int r;	
	char msg[BFSZ];
	strcpy(msg,"");
	if (!dl_init) {
		imsg("chan: Opening libauth.so\n");
		dl_lib = dlopen(file_config("libauth.so"),RTLD_LAZY);
		if (dl_lib==NULL) {filemsg("Could not open libauth.so:  %s\n",dlerror()); return FALSE;}
		dl_init = TRUE;
	}
	if (auth_group==NULL) {
		auth_group = dlsym(dl_lib,"auth_group");
		if (auth_group==NULL) {filemsg("Unable to find auth_group() symbol %s\n",dlerror()); return FALSE;}
		imsg("lib: Calling dll function to check user (%s)\n",user);
	}
	r = (*auth_group)(user,group,type,msg);
	if (strlen(msg)>0) imsg("auth_group: %s",msg);
	return r;
}

#else
int dll_group(char *user, char *group, int type)
{                    
   return TRUE;
}
int dll_auth(char *ipname, char *ipnum, char *user, char *pass
	     ,char *postgrps, char *readgrps, int *isauth)
{
	return TRUE;
}
int dnewsauth(char *user, char *pass, char *usergrps, int bfsz)
{
	return TRUE;
}
#endif
#endif


/*----------------- User Info recording ----------------*/
static int ui_xtime[MAX_CHAN];
static int ui_nrej[MAX_CHAN];
static int ui_nread[MAX_CHAN];
static int ui_nbytes[MAX_CHAN];
static int ui_nxov[MAX_CHAN];
static int ui_ngrp[MAX_CHAN];
static int ui_grp[MAX_CHAN];
void user_addinfo(int c,int nread, int nxov, int nbytes)
{
	ui_nread[c] += nread;
	ui_nxov[c] += nxov;
	ui_nbytes[c] += nbytes;
}
void user_starttime(int c)
{
	ui_xtime[c] = lib_gmtime();
}
void user_addgrp(int c, int g)
{
	if (ui_grp[c] != g) {
		ui_grp[c] = g;
		ui_ngrp[c]++;
	}
}
void user_addrej(int c)
{
	ui_nrej[c]++;
}
#define UNAMLEN 60
typedef struct UINFO {int nread,nxov,nbytes,nrej,ngrp,xtime; char name[UNAMLEN]; struct UINFO *next;} UInfo;
static UInfo *ulist;
int user_cmp(const void *aa, const void *bb);
int user_cmp(const void *aa, const void *bb)
{
	UInfo *a = *((UInfo **) aa);
	UInfo *b = *((UInfo **) bb);
/*	imsg("a->nread %d %d  %s %s\n",a->nread,b->nread,a->name,b->name);*/
	return (a->nread - b->nread);
}
static int utot;
void user_clear(void)
{
	UInfo *u,*un;
	for (u=ulist;u!= NULL;u = un) {
		un = u->next;
		myfree(u);
	}
	ulist = NULL;
	utot = 0;

}
void user_report(int chan)
{
	UInfo *u;
	char bf[BFSZ];
	int n=0;
	UInfo **p;
	int i;
	UInfo tot;
	for (u=ulist;u!=NULL;u=u->next) n++;
	p = mymalloc(n*(sizeof(p)));
	for (u=ulist,i=0;u!=NULL;u=u->next,i++) p[i] = u;
	qsort(p,n,sizeof(p),user_cmp);

	tot.ngrp = tot.nread = tot.nxov = tot.nbytes = 0;
	tot.xtime = tot.nrej = 0;
		
	sprintf(bf,"  NGroup,   NRead,    NXov,  Nbytes,    Time,    NRej, Names\r\n");
	chan_write_s(chan,bf);
	for (i=0; i<n; i++) {
		u = p[i];
		sprintf(bf,"%8d %8d %8d %9dk %7dm %8d %s\r\n",u->ngrp,u->nread,u->nxov,u->nbytes/1000,u->xtime/60,u->nrej,u->name);
		chan_write_s(chan,bf);
		tot.ngrp += u->ngrp;
		tot.nxov += u->nxov;
		tot.nread += u->nread;
		if (u->nbytes<0) u->nbytes = 0;
		tot.nbytes += u->nbytes/1000;
		tot.xtime += u->xtime;
		tot.nrej += u->nrej;
	}
	u = &tot;
	sprintf(bf,"%8d %8d %8d %9dm %7dm %8d %s\r\n",u->ngrp,u->nread,u->nxov,u->nbytes/1000,u->xtime/(60),u->nrej,"Grand Totals");
	chan_write_s(chan,bf);

	if (p!=NULL) myfree(p);
}
UInfo *user_findadd(char *name);
void user_record(int c)
{
	char uname[BFSZ],bf[BFSZ];
	char uuser[BFSZ];
	UInfo *u;

	ncpy(bf,chan_getipname(c),UNAMLEN-1);
	ncpy(uuser,chan_getuser(c),UNAMLEN-1);
	sprintf(uname,"%s:%s",bf,uuser);
	if (config_true(C_SHOWUSERS_BYUSER)) {
		if (strlen(uuser)>0) {
			strcpy(uname,uuser);
		}
	}
	if (strlen(uuser)==0) strcpy(uname,bf);
	uname[UNAMLEN-1] = 0;
	
	u = user_findadd(uname);
	if (u==NULL) return;
	u->nread += ui_nread[c];
	u->nxov += ui_nxov[c];
	u->nbytes += ui_nbytes[c];
	u->nrej += ui_nrej[c];
	u->ngrp += ui_ngrp[c];
	if (ui_xtime[c]!=0) u->xtime += lib_gmtime() - ui_xtime[c];
	ui_nread[c] = 0;
	ui_nrej[c] = 0;
	ui_ngrp[c] = 0;
	ui_grp[c] = -1;
	ui_nxov[c] = 0;
	ui_nbytes[c] = 0;
	ui_xtime[c] = 0;
}
UInfo *user_findadd(char *name)
{
	UInfo *u;
	u = ulist;
	if (u==NULL) {
		ulist = u = mymalloc(sizeof(*u)); 		
		memset(u,0,sizeof(*u));
		ncpy(u->name,name,UNAMLEN-1);
		u->next = NULL;
	}
	for (;u->next != NULL;) {
		if (strcmp(u->name,name)==0) return u;
		u = u->next;
	}
	if (strcmp(u->name,name)==0) return u;
	if (utot>5000) return NULL;
	u->next = mymalloc(sizeof(*u));
	utot++;
	u = u->next;
	memset(u,0,sizeof(*u));
	ncpy(u->name,name,UNAMLEN-1);
	u->next = NULL;

	return u;
}

static int drop_item = FALSE;
void chan_purge_ihave(void)
{
        int c;
	char bf[BFSZ];
        int t = lib_gmtime();
	int tout = atoi(config_get(C_IHAVE_TIMEOUT));

        if (tout==0) tout = 600;
	for (c=0; c<nchan; c++) { /* change to 60*60 */
          if (chan_isopen(c)) {
		if (chan_state[c] == C_IHAVE) {
		 if (t-chan_lasttime[c] > tout) {
			 emsg("Channel in ihave state but no data arrived for %d seconds %d: you can set IHAVE_TIMEOUT higher if needed\n",t-chan_lasttime[c],c);
			if (xbuf[c]!=NULL) {
				ncpy(bf,xbuf[c],100);
				emsg("Data pending on channel (%s) \n",bf);
			}
			strcpy(bf,".\r\n");
			drop_item = TRUE;
			chan_read(c,bf,3);
			drop_item = FALSE;
			xn[c] = 0;
		 }
		}
	  }
	}
}
int chan_isdrop(int c)
{
	return drop_item;
}

/*-----------------------------------------------------*/
#ifndef WIN32
int ras_dial(char *site)
{
	return TRUE;
}
void ras_exit(void)
{

}
int ras_hangup(void)
{
	return FALSE;
}
#else
static HRASCONN rascon;


static HINSTANCE raslib;
static FARPROC prasdial,prashangup,prasgeterrorstring;

int ras_dial(char *site)
{
	static RASDIALPARAMS dial;
	static int done_init;
	char num[BFSZ];
	char bf[BFSZ];
	char wild[BFSZ];
	int r;
	static int ntries;
	static int thishour;
	time_t tt;
	struct tm *t;
	int maxcalls;
	time(&tt);
	t = localtime(&tt);
	if (t->tm_hour!=thishour) {
		ntries = 0;
		thishour = t->tm_hour;
	}
	strcpy(num,config_get(C_RAS_NUMBER)); 
	if (strlen(config_get(C_RAS_ENTRY))==0) if (strlen(num)==0) return TRUE;	
	if (rascon!=NULL) {
		dmsg("ras: Connection already open\n");
		return TRUE;
	}
	strcpy(wild,config_get(C_RAS_LIST));
	if (strcmp(wild,"")!=0) {
		if (!group_match(wild,site)) {
			dmsg("ras: Site not in ras_list (%s) (%s)\n",wild,site);
			return TRUE;
		}
	}

	dial.dwSize = sizeof(dial);
	strcpy(dial.szEntryName,config_get(C_RAS_ENTRY));
	strcpy(dial.szPhoneNumber,num);
	strcpy(dial.szCallbackNumber,"");
	strcpy(dial.szUserName,config_get(C_RAS_USERNAME));
	strcpy(dial.szPassword,config_get(C_RAS_PASSWORD));
	strcpy(dial.szDomain,config_get(C_RAS_DOMAIN));
	imsgf("ras: Attempting to establish RAS connection to (%s) (%s)\n",config_get(C_RAS_ENTRY),config_get(C_RAS_NUMBER));

	if (!done_init) {
		done_init = TRUE;
		raslib = LoadLibrary("rasapi32");
		if (raslib==NULL) {emsg("Cannot load rasapi32.dll %d\n",GetLastError()); return FALSE;}
		prasdial = GetProcAddress(raslib,"RasDialA");
		if (prasdial==NULL) {emsg("Could not find entry point: RasDialA\n"); return FALSE;}
		prashangup = GetProcAddress(raslib,"RasHangUpA");
		if (prasdial==NULL) {emsg("Could not find entry point: RasHangUpA\n"); return FALSE;}
		prasgeterrorstring = GetProcAddress(raslib,"RasGetErrorStringA");
	}	
	if (prasdial==NULL) return FALSE;	
	maxcalls = atoi(config_get(C_RAS_MAXCALLS));
	if (maxcalls==0) maxcalls = 10;
	if (maxcalls<=ntries) {
		imsg("Not redialing as ras_maxcalls exceeded for this hour %d\n",ntries);
		return FALSE;
	}
	ntries++;
	r = prasdial(NULL,NULL,&dial,1,NULL,&rascon);
	if (r!=0) {
		strcpy(bf,"");
		if (prasgeterrorstring!=NULL) prasgeterrorstring(r,bf,BFSZ-1);
		emsg("RAS dial failed, (%s) raserr=%d, error %d\n",bf,r,GetLastError());
		if (rascon!=NULL) prashangup(rascon);
		rascon = NULL;
		return FALSE;
	}
	dmsg("ras: Connection made ok\n");
	return TRUE;
}
void ras_exit(void)
{
	if (!ras_hangup()) return;
	lib_sleep(3);
}
int ras_hangup(void)
{
	if (prashangup==NULL) return FALSE;
	if (rascon!=NULL) {
		imsg("ras: Hanging up phone\n");			
		prashangup(rascon);
		rascon = NULL;
		return TRUE;
	}
	return FALSE;
}
#endif




#ifdef PASS_SHADOW
#define PASS_STD TRUE
#endif

#ifdef PASS_STD
#define DONE_LOOKUP TRUE
#include <unistd.h>
#include <pwd.h>
#ifdef PASS_SHADOW
#include <shadow.h>
#endif
int unix_lookup(char *user, char *pass){
#ifdef PASS_SHADOW
    struct spwd *pwd;
#endif
    struct passwd *pw;
    long today;
	char xpass[300];
   	
#ifdef PASS_SHADOW
	/*  Look for the user in the shadow password file */
	if ((pwd = getspnam(user)) == NULL) {
		/* but maybe he is not using shadow passwords */
		fimsg("Could not get shadow password info for %s\n",user); 
#endif
		if ((pw = getpwnam(user))==NULL){
			imsg("Account doesn't exist (%s)\n",user);
			return FALSE;
		}else{
			if (pw->pw_passwd==NULL) {
				imsg("No password for (%s) \n",user);
				return TRUE;
			}else{
				strcpy(xpass,pw->pw_passwd);
				goto no_shadow;
			}
		}
#ifdef PASS_SHADOW
	}
	today = (long)time((time_t *)NULL)/24/60/60;

	/* Check for expiration date */
	if (pwd->sp_expire > 0 && today > pwd->sp_expire) {
		imsg("Account password expired (%s)\n",user);
		return FALSE;
	}

	/* Check if password is valid */
	if (pwd->sp_max > 0 && today > pwd->sp_lstchg+pwd->sp_max) {
		imsg("Account password expired (%s)\n",user);
		return FALSE;
	}

	if (pwd->sp_pwdp==NULL) {
		imsg("No password for (%s) \n",user);
		return TRUE;
	}

	strcpy(xpass,pwd->sp_pwdp);
	endspent();
#endif

no_shadow:	
	if (strlen(xpass)==0) {
		imsg("No password for (%s) \n",user);
		return TRUE;
	}

	if (strcmp((char *) crypt(pass, xpass), xpass)==0){
		/*** get home dir from structure  into *home if not NULL*/
		return TRUE;
	}
	dmsg("chan: Password failed for user (%s) (%s)!=(%s)\n",user,xpass,crypt(pass,xpass));
	/* if (strcmp(pw_encrypt(pass,xpass),xpass)==0) 	return TRUE;*/
	imsg("Password failed for user (%s), you might need to run dnews as root to access the shadow password file \n",user);
	return FALSE;
}
#endif

#ifdef VMS
#define DONE_LOOKUP TRUE


#include stdio
#include descrip
#include string
#include uaidef  
/* if need be, extract $uaidef out of starlet.mlb and create
                       a header file.  Command:

                        $library/extract=($uaidef)/output=uaidef.h -
                                sys$library:starlet.mlb

                       Edit the .h file and delete macro code at the
                       beginning and end of the file and replace all
                       occurences of $EQU with #define.
*/
int unix_lookup( char *the_username, char *the_password )
{

    struct
       {
       short length, code;
       void *buff_addr, *retlen_addr;
       } item_list[5];

    int status,flags,hash_pwd1[2],hash_pwd2[2];
    short salt;
    char alg;
    char nam[12], passwrd[32];
    $DESCRIPTOR(name,nam);
    $DESCRIPTOR(password,passwrd);

    strcpy(nam, the_username);

    if (strlen(nam) > 12) return 0; 

    name.dsc$w_length = strlen(nam);         /*reset the length of the
                                              name to exclude the NULL
                                              terminator
                                             */
    strcpy(passwrd, the_password);
    password.dsc$w_length = strlen(passwrd);

/*
   User name and password need to be in upper case so we can get a 
match.
*/

    if (((status=STR$UPCASE(&name,&name))&1)!=1) LIB$STOP(status);
    if (((status=STR$UPCASE(&password,&password))&1)!=1) 
LIB$STOP(status);


/*
   Build the item list used in the call to SYS$GETUAI. These items 
are
   needed by SYS$HASH_PASSWORD.
*/
    item_list[0].length    =  1;
    item_list[0].code      =  UAI$_ENCRYPT;
    item_list[0].buff_addr =  &alg;
    item_list[0].retlen_addr  =  0;         /*Not used */

    item_list[1].length    =  2;
    item_list[1].code      =  UAI$_SALT;
    item_list[1].buff_addr =  &salt;
    item_list[1].retlen_addr  =  0;        /*Not used */

    item_list[2].length    =  8;
    item_list[2].code      =  UAI$_PWD;
    item_list[2].buff_addr =  &hash_pwd1;
    item_list[2].retlen_addr  =  0;        /*Not used */

    item_list[3].length    =  4;
    item_list[3].code      =  UAI$_FLAGS;
    item_list[3].buff_addr =  &flags;
    item_list[3].retlen_addr  =  0;        /*Not used */

    item_list[4].length    =  0;
    item_list[4].code      =  0;

    if(((status=SYS$GETUAI(0,0,&name,&item_list,0,0,0))&1) !=1)
         return 0;

    if (flags & UAI$M_DISACNT) return 0;


/*  This service takes a username/password combo and returns a 
hashed,
    quadword password.  The user must supply the encryption algorithm
    and the salt used to hash the password in the first place.  This
    information is available through SYS$GETUAI.
*/

    if(((status=SYS$HASH_PASSWORD(&password,alg,salt,&name,hash_pwd2))&  
1)
       !=1) return 0;

    if ((hash_pwd1[0] == hash_pwd2[0]) && (hash_pwd1[1] == 
hash_pwd2[1]))
         return 1;

    return 0;

}
#endif



#ifndef DONE_LOOKUP
int unix_lookup(char *user, char *pass)
{
	return FALSE;
}
#endif

int ip_match_one(char *aawild, char *aaxnum);
int ip_match(char *wild, char *aaxnum)
{
	char bf[BFSZ];
	char *w[200];
	int i;
	int nw;
	ncpy(bf,wild,BFSZ-1);
	nw = token_split(bf, w, ",", 100);

	for (i=0; i<nw; i++) {
		if (ip_match_one(w[i],aaxnum)) return TRUE;		
	}
	return FALSE;
}
int ipmask_match(char *fields, char *ip);
int ip_match_one(char *aawild, char *aaxnum)
{
	char wild[BFSZ],xnum[BFSZ];
	char *w[100];
	char *n[100];
	char *s;
	int i;
	int nw,nn,xfrom,xto,xn;
	ncpy(wild,aawild,BFSZ-1);
	ncpy(xnum,aaxnum,BFSZ-1);

	if (!isdigit(*wild) || !isdigit(*xnum)) return group_match(wild,xnum);
	if (strchr(wild,'*')!=NULL) return group_match(wild,xnum);
	if (strchr(wild,'/')!=NULL)  return ipmask_match(wild,xnum);
	nw = token_split(wild, w, ".", 100);
	nn = token_split(xnum, n, ".", 100);
	if (nw!=nn) return FALSE;
	for (i=0; i<nw; i++) {
		if (strcmp(w[i],n[i])==0) continue;
		s = strchr(w[i],'-');
		if (s==NULL) return FALSE;
		*s = 0;
		xfrom = atoi(w[i]);
		xto = atoi(s+1);
		xn = atoi(n[i]);
		dmsg("chan: IP Range %d-%d  %d\n",xfrom,xto,xn);
		if (xn>=xfrom) if (xn<=xto) continue;
		return FALSE;
	}
	return TRUE;		
}

/* Win32 dll for external authentication*/
#ifdef WIN32
static HINSTANCE dlx_lib;
int dnewsauth(char *user, char *pass, char *usergrps, int bfsz)
{
	static FARPROC dauth;
	/**(*dauth)(char *user, char *pass, char *usergrps, int bfsz, char *msg);*/
	char reason[BFSZ];
	char libname[BFSZ];
	int r;
	strcpy(reason,"");
	if (dlx_lib==NULL) {
		strcpy(libname,"dnewsauth");
		if (strlen(config_get(C_AUTH_LIB))>0) {
			strcpy(libname,config_get(C_AUTH_LIB));
		}
		dlx_lib = LoadLibrary(libname);
		if (dlx_lib==NULL) {emsg("Cannot load (%s) err=%d\n",libname,GetLastError()); return FALSE;}
	}	
	if (dlx_lib==NULL) return FALSE;	
	if (dauth==NULL) {
		dauth = GetProcAddress(dlx_lib,"dnewsauth");
		if (dauth==NULL) {emsg("Could not find entry point: dnewsauth()\n"); return FALSE;}
	}
	dmsg("dll: Calling dll function user(%s) pass(%s)\n",user,pass);
	strcpy(usergrps,"");
/*	imsg("usergroups %s, bfsz %d, reason %s\n",usergrps,bfsz,reason);*/
	r = (*dauth)(user,pass,usergrps,bfsz,reason);
	dmsg("dll: function response %d\n",r);
	if (strlen(reason)>0) imsg("dnewsauth: %.80s\n",reason);
	return r;
}

#endif



static int cstat_time[MAX_CHAN];
static int cstat_in[MAX_CHAN];
static int cstat_out[MAX_CHAN];
static int cstat_nread[MAX_CHAN];
void cstat_addin(int c, int n)
{
	cstat_in[c] += n;
}
void cstat_addout(int c, int n)
{
	cstat_out[c] += n;
}
void cstat_addnread(int c, int n)
{
	cstat_nread[c] += n;
}
void cstat_init(int chan)
{
	cstat_time[chan] = lib_gmtime();
	cstat_in[chan] = 0;
	cstat_out[chan] = 0;
	cstat_nread[chan] = 0;
}
void cstat_log(int c)
{
	FILE *f;
        time_t ts;
        struct tm *t; 
        ts = time(NULL);
        t = localtime(&ts);

	if (c<0) return;
	if (!config_true(C_LOG_IO)) return;
	f = fopen(cstat_fname(),"a");
	if (f==NULL) { filemsg("Could not append to %s\n",cstat_fname()); return;}
        fprintf(f,"%02d%02d%02d/%02d:%02d:%02d/IO1/%d/%s/%s/%d/%d/%d/\n"
        	,t->tm_year,t->tm_mon+1,t->tm_mday
                ,t->tm_hour,t->tm_min,t->tm_sec
		,lib_gmtime()-cstat_time[c]
                ,chan_getuser(c),chan_getipname(c)
		,cstat_in[c],cstat_out[c],cstat_nread[c]);
	fclose(f);
	cstat_init(c);
        return;
}
char *cstat_fname(void)
{
 	static char bf[BFSZ];
        time_t ts;
        struct tm *t; 
        ts = time(NULL);
        t = localtime(&ts);			  

	sprintf(bf,"io_%04d%02d%02d.log",t->tm_year+1900,t->tm_mon+1,t->tm_mday);
	return file_log(bf);
}
unsigned long my_cinet_addr(char *ip);
int ipmask_match(char *iprange,char *ipcheck)
{
	char buf[BFSZ];
	char *ipbase_buf;
	char *nbits_buf;
	unsigned long ipbase;
	unsigned long ip;
	int nbits = 32;


        if (strchr(iprange, '/')==NULL)  return FALSE;

	ip = my_cinet_addr(ipcheck);
	strcpy(buf,iprange);
	ipbase_buf = buf;
	nbits_buf = strchr(buf,'/');
	if (nbits_buf==NULL) return FALSE;
	nbits_buf[0]='\0';
	nbits_buf++;
	nbits = 32 - atoi(nbits_buf);
	if ((nbits>32) || (nbits<0)) return FALSE;
	ipbase = my_cinet_addr(ipbase_buf);
	return ((ipbase >> nbits)==(ip >> nbits)) ? 1 : 0;
}

unsigned long my_cinet_addr(char *ip)
{
	int i;
	unsigned long temp = 0;
	short temp2 = 0;
	char *ch;
	char *ch2;
	char ipbuf[BFSZ];

	strcpy(ipbuf,ip);
	ch=ch2=ipbuf;
	for (i=0;i<4;i++)
	{
		ch = strchr(ch2,'.');
		if (ch != NULL) ch[0] = '\0';
		temp2 = atoi(ch2);
		if ((temp2<0)||(temp2>255)) return 0;
		temp = (temp << 8)|(long)temp2;
		if (ch==NULL) break;
		ch2=++ch;
	}
	return temp;
}
void chan_writelog(int c, int isread, char *s, int len)
{	
	static int lastchan= -2;
	char bf[BFSZ];
	char *dstr;
	static int loglen[MAX_CHAN]; 


	if (lastchan!=c) if (flog!=NULL) {fclose(flog); flog = NULL;}
	if (loglen[c]>3000000) {
		if (flog!=NULL) fclose(flog); flog = NULL;
		sprintf(bf,"chan_%d.log",c);
		remove(file_log(bf));
		loglen[c] = 0;
	}

	if (flog==NULL) {
		sprintf(bf,"chan_%d.log",c);
		flog = myfopen(file_log(bf),"a");
		if (flog==NULL) {filemsg("Cannot write to %s",file_log(bf)); return;}
	}
	lastchan = c;
	if (isread) dstr="--> ";
	else dstr = "<-- ";
	if (config_true(C_CHAN_LOGALL)) {
		fprintf(flog,"%s{",dstr,bf);
		fwrite(s,1,len,flog);
		fprintf(flog,"}\n");
		loglen[c] += len;
		return;
	}
	if (len>200) len = 200;
	ncpy(bf,s,len);
	s = strchr(bf,'\015'); if (s!=NULL) *s = 0;
	s = strchr(bf,'\012'); if (s!=NULL) *s = 0;


#ifdef WIN32
	{
		SYSTEMTIME st;
		GetSystemTime(&st);	
		fprintf(flog,"%02d:%02d.%d %s%s\n"
			,st.wMinute,st.wSecond,st.wMilliseconds			
			,dstr,bf);
	}
#else
	fprintf(flog,"%s%s\n",dstr,bf);
#endif
}
int user_readart(int chan, int type, char *ipname, char *ipnumber, char *username, char *password, char *group, int article, int size, char *emsg) ;
int chan_readart(int chan, char *gname, int article, int size, char *reason)
{
	/* types 1 = reading an article */
	return user_readart(chan,1,chan_getipname(chan),chan_getipnum(chan),
		chan_getuser(chan),chan_getpass(chan),gname,article,size,reason);

}
#ifdef WIN32
int user_readart(int chan, int type, char *ipname, char *ipnumber, char *username, char *password, char *group, int article, int size, char *reason) 
{
	static FARPROC dreadart;
	char libname[BFSZ];
	int r;
	strcpy(reason,"");
	if (dlx_lib==NULL) {
		strcpy(libname,"dnewsauth");
		if (strlen(config_get(C_AUTH_LIB))>0) {
			strcpy(libname,config_get(C_AUTH_LIB));
		}
		dlx_lib = LoadLibrary(libname);
		if (dlx_lib==NULL) {emsg("Cannot load (%s) err=%d\n",libname,GetLastError()); return FALSE;}
	}	
	if (dlx_lib==NULL) return FALSE;	

	if (dreadart==NULL) {
		dreadart = GetProcAddress(dlx_lib,"auth_readart");
		if (dreadart==NULL) { emsg("Could not find entry point: auth_readart()\n"); return FALSE; }
	}
	
	dmsg("readart: [%d] t=%d ip=%s u=%s group=%s:%d len=%d\n",chan,type,ipname,username,group,article,size);
	r = (*dreadart)(chan,type,ipname,ipnumber,username,password,group,article,size,reason);
	dmsg("readart: function returned %d %s\n",r,reason);
	if (strlen(reason)>0) imsg("readart: function returned %d %s\n",r,reason);
	return r;
}
#else
#ifdef HAS_DLOPEN
int user_readart(int chan, int type, char *ipname, char *ipnumber, char *username, char *password, char *group, int article, int size, char *reason) 
{
	static int (*dreadart)(int chan, int type, char *ipname, char *ipnumber, char *username, char *password, char *group, int article, int size, char *reason) ;
	int r;
	char libname[BFSZ];
	strcpy(reason,"");
	if (!dlx_init) {
		strcpy(libname,"dnewsauth.so");
		if (strlen(config_get(C_AUTH_LIB))>0) {
			strcpy(libname,config_get(C_AUTH_LIB));
		}
		imsg("chan: Opening %s\n",libname);
		dlx_lib = dlopen(libname,RTLD_LAZY);
		if (dlx_lib==NULL) {filemsg("Could not open %s:  %s\n",libname,dlerror()); return FALSE;}
		dlx_init = TRUE;
	}
	if (dreadart==NULL) {
		dreadart = dlsym(dlx_lib,"auth_readart");
		if (dreadart==NULL) {filemsg("Unable to find auth_readart() symbol %s\n",dlerror()); return FALSE;}
	}
	dmsg("readart: [%d] t=%d ip=%s u=%s group=%s:%d len=%d\n",chan,type,ipname,username,group,article,size);
	strcpy(reason,"");
	r = (*dreadart)(chan,type,ipname,ipnumber,username,password,group,article,size,reason);
	dmsg("readart: function returned %d %s\n",r,reason);
	if (strlen(reason)>0) imsg("readart: function returned %d %s\n",r,reason);
	return r;
}
#else
int user_readart(int chan, int type, char *ipname, char *ipnumber, char *username, char *password, char *group, int article, int size, char *reason) 
{
	imsg("readart not linked into this platform sorry\n");
	return TRUE;
}
#endif
#endif
/*
	int user_readart(int chan, int type,
		 char *ipname,
                 char *ipnumber, 
                 char *username,
                 char *password,
                 char *group,
                 int article,
                 int size,
                 char *emsg) 

        return TRUE = ok
        return FALSE = disconnect user.
        emsg = reason to report to user on disconnect.
*/
