/*
// reduced calls to 'emsg' which seem to relate to mutex deadlocks in some way I don't fully understand....

  External auth processes

  Spawn upto  'n' auth processes

  When request comes in, send to free process
  If none free, then sit in loop polling until one finishes

  If used with thread then define DO_THREADS before compiling

  Calling sequence for this module:

	ex_init(imsg,emsg,3);   To use 3 authent processes
	ex_cmd(char *cmdline, char *result, int *done)
	for (;!done;) {
		ex_poll();
		lib_msleep(10);
	}

  Or use:
	int ex_check(char *user, char *pass, char *result)

*/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "lib.h"
#include "lprintf.h"
#include "lib_spawn.h"
#include "ex_auth.h"
#define DMID 14
#include "debug.h"
#ifdef DO_THREADS
#include "thread.h"
#endif

#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif

#define MAXEX 20
#include "defines.h"
static char *presult[MAXEX];
static int presultsz[MAXEX];
static time_t pstarted[MAXEX];
static int *pdone[MAXEX];
static int ndone[MAXEX];
static int (*pcb[MAXEX])(void *p1, char *s);
static void *pcb_p1[MAXEX];
static IHANDLE pin[MAXEX];
static IHANDLE pout[MAXEX];
static char *pbf[MAXEX];
static int npbf[MAXEX];
static pid_t pid[MAXEX];
static int broken=FALSE;
static int pstate[MAXEX];
static time_t p_tout[MAXEX];
static char *ex_user[MAXEX];
char * ex_encode_spaces(char *pass, char *in);

static char *backup_pbf[MAXEX];
static char *backup_presult[MAXEX];
static int backup_presultsz[MAXEX];

enum {P_NONE,P_READY,P_BUSY};
static int (*emsg)( const char *format ,...);
static int (*imsg)( const char *format ,...);
int ex_spawn(int i);
int ex_startall(void);
int ex_restart_one(int i, char *reason);
static int is_shutdown;
static char spawn_cmd[BFSZ];
static int authent_n;
static int mat;
/* RMH - added to cause logging of authent lines */
static int global_ex_logall;
/* RMH - added to stop weird ascii chars */
static int blockascii;

#ifdef DO_THREADS
static Mutex *m;
#endif

void ex_exit(void)
{
	int i;
	for(i=0;i<MAXEX;i++) {
		if (ex_user[i]!=NULL) free(ex_user[i]);
		ex_user[i]=NULL;
	}
}

void ex_mutex_init(void)
{
#ifdef DO_THREADS
	m = th_mutex_new("ex_mutex");
#endif
}
static int restart;
void ex_setrestart(int x)
{
	restart = x;
}
static time_t  gottime;
static int lastgot,lastrel;
#ifndef MUTEX_TIME
#define MUTEX_TIME 70000
#endif
static int gotpid;

#define MAX_ENV 100
static char *ex_env[MAX_ENV];
static int ex_nenv;
static char *last_search_src;
static int last_search_line;


static int mutex_i;
static int (*mutex_cb)(int vm, int isget, int seconds, char *file, int line);
int ex_auth_set_mutex(int (*cb)(int vm, int isget, int seconds, char *file, int line), int m)
{
		mutex_i = m;
		mutex_cb = cb;
		return TRUE;
}


int ex_mutex_wait(int line)
{
	static int done;
	if (mutex_cb) return mutex_cb(mutex_i,TRUE,600,__FILE__,line);
#ifdef DO_THREADS
	if (m==NULL) return TRUE;
	if (!th_mutex_wait(m,MUTEX_TIME)) { /* 10 minutes */
		if (emsg) emsg("ex_auth: Signal Cannot ex_mutex_wait, got %d mat=%d released %d, %d seconds, held th=%d search %s:%d\n",lastgot,mat,lastrel,time(NULL)-gottime,gotpid,last_search_src,last_search_line);

//		printf("Cannot ex_mutex_wait, fatal problem, dieing malloc\n");
		if (!done) {
			done = TRUE;
			lib_abort();
		}
		lib_msleep(15000);
		lib_abort();
	}
	lastgot = line;
	gottime = time(NULL);
	gotpid = th_id();
#endif
	return TRUE;
}
void ex_mutex_release(int line)
{
	if (mutex_cb) { mutex_cb(mutex_i,FALSE,600,__FILE__,line); return; }
#ifdef DO_THREADS
	if (m==NULL) return;
	lastrel = line;
	th_mutex_release(m);
#endif
}
int ex_n(void)
{
	return authent_n;
}
int ex_init(int (*vimsg)( const char *format ,...),int (*vemsg)( const char *format ,...),int n, char *scmd)
{
	authent_n = n;
	ex_init_spawn_cmd(scmd);
	imsg = vimsg;
	emsg = vemsg;
	if (n>=MAXEX) n = MAXEX-1;
	ex_mutex_init();
	ex_startall();
	return TRUE;
}
int ex_startall(void)
{
	int i;
	//if (imsg) imsg("ex_auth: startall [%d] \n",authent_n);
	ex_mutex_wait(__LINE__);
//	lib_msleep(9000);
	for (i=0; i<authent_n; i++) {
		if (pid[i]==0) {
			if (imsg) imsg("ex_auth: Starting [%d]\n",i);
			if (ex_spawn(i)) {
				pstate[i] = P_READY;
				if (imsg) imsg("ex_auth: ready [%d]\n",i);
			} else {
				broken = TRUE;
			}
		}
	}
	ex_mutex_release(__LINE__);
	return TRUE;
}
void ex_init_env(char **env) {
	if (!env) {
		ex_nenv = lib_spawn_loadenv("authent.env",ex_env,MAX_ENV);
		if (ex_nenv == 0) ex_nenv = lib_spawn_setenv(ex_env);
	}
	else {
		int i;
		for(i = 0; env[i] && ex_nenv < MAX_ENV-1; i++)
			lib_spawn_addenv_real(ex_env,&ex_nenv,env[i]);
	}
}
int ex_init_addenv(char *label, char *value) {
	if (ex_nenv == MAX_ENV-1) return FALSE;
	lib_spawn_addenv(ex_env,&ex_nenv,label,value);
	return TRUE;
}
void ex_init_spawn_cmd(char *scmd) {
	ncpy(spawn_cmd,scmd,200);
}
int ex_restart(void)
{
	if (is_shutdown) return TRUE;
	if (imsg) imsg("ex_auth: restart called, first shutting down old ones\n");
	ex_shutdown();
	if (imsg) imsg("ex_auth: restart: now starting new ones\n");
	ex_startall();
	if (imsg) imsg("ex_auth: restart: finished\n");
	return TRUE;
}
int ex_goodascii(char *cmdline)
{
	char *p;

	for(p = cmdline; *p; p++) {
		if (*p < 32) break;
		if (*p >= 127) break;
	}

	return *p == 0;
}
void ex_dump(char *cmdline)
{
	char hexbf[BFSZ];
	char *p,*d;
	int len;

	emsg("ex_dump: Bad command line: len=%d\n",lib_strlen(cmdline));
	emsg("ex_dump: Bad command line: (%s)",cmdline);
	for(p = cmdline; *p; ) {
		d = hexbf;
		for(; *p && d-hexbf < BFSZ-10; p++)
		{
			len = sprintf(d,"(%c)(%02x) ",*p,*p);
			if (len <= 0) continue;
			d += len;
		}
		*d = 0;
		emsg("ex_dump: %s",hexbf);
	}
}
int parse_cmd_for_username(char *cmdline, char *user, int bfsz)
{
	char *tmp = strdup(cmdline);
	int rval = FALSE;
	char *p,*n;

	//[check|lookup|..] <username>[\r\n| <password>]
	p = mystrtok(tmp," \r\n",&n);
	if (!p) goto end;

	//<username>[\r\n| <password>]
	p = mystrtok(NULL," \r\n",&n);
	if (!p) goto end;

	ncpy(user,p,bfsz-1);
	rval = TRUE;
end:
	free(tmp);
	return rval;
}
int ex_cmd(char *cmdline, char *result, int bfsz, int *done, int (*fn)(void *p1, char *s), void *p1, char *user)
{

	int n = ex_n();
	int i;
	static int robin;
	int eshown=0;
	*done = FALSE;
	if (imsg==NULL) {
		strcpy(result,"-ERR ex_init was not called");
		*done = TRUE;
		return 0;
	}
	if (blockascii && !ex_goodascii(cmdline)) {
		char user[BFSZ];

		if (parse_cmd_for_username(cmdline,user,BFSZ))
			lprintf(result,bfsz,"-ERR %s command contains ascii character(s) outside the range 32-126, try g_authent_allow_badascii",user);
		else
			lprintf(result,bfsz,"-ERR command is malformed and contains ascii character(s) outside the range 32-126, setting g_authent_allow_badascii");
		if (imsg) imsg("response: (%s)\n",result);
//		if (imsg) ex_dump(cmdline);
		*done = TRUE;
		return 0;
	}
	ex_mutex_wait(__LINE__);
	for (;TRUE;) {
		robin++;
		if (robin>=n) robin = 0;
		for (i=robin; i<n; i++) {
			if (pstate[i]==P_READY) {
				if ((restart>0) && (ndone[i]>restart)) {
				//	if (imsg) imsg("ex_auth: %d lookup done, restart after %d, restarting...",ndone[i],restart);
					ex_restart_one(i,"auto restart");
				} else {
					ex_set_cb(i,fn,p1);
					if (ex_user[i]!=NULL) free(ex_user[i]);
					// LINE SPACING :-)
					ex_user[i] = strdup(user); // THIS LINE DOES A HUGE AMOUNT... 4500 ALLOCATIONS... 139mb,  
					ex_do_cmd(i,cmdline,result,bfsz,done);
					ex_mutex_release(__LINE__);
					return i;
				}
			}
		}
		for (i=0; i<n; i++) {
			if (pstate[i]==P_READY) {
				if ((restart>0) && (ndone[i]>restart)) {
					if (imsg) imsg("ex_auth: %d lookup done, restart after %d, restarting...",ndone[i],restart);
					ex_restart_one(i,"auto restart2");
				} else {
					ex_set_cb(i,fn,p1);
					if (ex_user[i]!=NULL) free(ex_user[i]);
					ex_user[i] = strdup(user);
					ex_do_cmd(i,cmdline,result,bfsz,done);
					ex_mutex_release(__LINE__);
					return i;
				}
			}
		}
		ex_mutex_release(__LINE__);
		if (eshown==0) lib_msleep(20);
		else lib_msleep(100);
		if (eshown%30==3 && imsg) imsg("ex_auth: Waiting for authent slave to lookup %s\n",user);
		if (eshown%41==40) {
			ex_mutex_wait(__LINE__); // In this code when it crashes... 
							// chrisp removed 'imsg/emsg' to avoid deadlock issues. 
						
			mat = 300;
			for (i=0; i<n; i++) {
				//if (imsg) imsg("ex_auth: busy[%d] state=%d user=%s age=%d\n",i,pstate[i],ex_user[i],time(NULL)-pstarted[i]);
				if (time(NULL)-pstarted[i]>900 || (time(NULL)>(p_tout[i]+90))) { // This timeout increased as the other one is more correct tout
					//if (emsg) emsg("ex_auth: busy[%d] state=%d user=%s age=%d\n",i,pstate[i],ex_user[i],time(NULL)-pstarted[i]);
					mat = 301;
					ex_restart_one(i,"ninety second restart while waiting");
					mat = 302;

				}
			}
			mat = 303;
			ex_mutex_release(__LINE__);
		}
		eshown++;
		ex_mutex_wait(__LINE__);
	}
	ex_mutex_release(__LINE__);
	return 0;
}
int ex_restart_one(int i, char *reason)
{
	int r;
	char cmdline[BFSZ];

	if (is_shutdown) return FALSE;
	//if (global_ex_logall && imsg) imsg("Authent[Restarting %d]\n",i);
	ndone[i] = 0;
	strcpy(cmdline,"exit\n");
	if (pin[i] > 0) {
		r = spawn_write_timed(pin[i],cmdline,lib_strlen(cmdline),100);
		lib_msleep(100);
	}
	ex_reset(i,reason);
	return TRUE;
}
int ex_spawn(int i)
{
	int r;
	char etext[BFSZ];
	if (is_shutdown) return FALSE;
	if (imsg) imsg("ex_auth: spawn(%d,%s)\n",i,spawn_cmd);
	if (ex_nenv) r = lib_spawn(spawn_cmd,ex_env,&pin[i],&pout[i],etext);
	else r = lib_spawn_noenv(spawn_cmd,&pin[i],&pout[i], etext);
	if (r<=0) {
		if (imsg) imsg("ex_auth: Could not spawn %.500s\n",etext);
		return FALSE;
	}
	if (imsg) imsg("ex_auth: Done spawn\n");
	pid[i] = r;
	npbf[i] = 0;
	return TRUE;
}
int ex_set_cb(int p, int (*fn)(void *x, char *p1), void *p1)
{
	pcb[p] = fn;
	pcb_p1[p] = p1;
	return TRUE;
}
int ex_search_from(char *wild, char *result, int bfsz, int (*fn)(void *p1, char *s), void *p1, char *src, int line)
{
	char bf[BFSZ];
	int k=0;
	int done=FALSE;
	time_t start = time(NULL);
	last_search_src = src;
	last_search_line = line;
	if (global_ex_logall && imsg) imsg("Authent[search %.100s]\n",wild);
	sprintf(bf,"search %.100s",wild);
	ex_cmd(bf,result,bfsz,&done,fn,p1,"none");
	for (;!done;) {
		k++;
		ex_poll();
		if (!done) if (k%90==1) lib_msleep(20);
	}
	last_search_src = src;
	last_search_line = line+1000000;

	if (result[0]=='+') return TRUE;
	if (time(NULL)-start>10) if (emsg) emsg("Authent: ex_search %s %s:%d took %d seconds\n",wild,src,line,time(NULL)-start);
	return FALSE;
}
static int ex_timeoutx = 90;
int ex_set_timeout(int x)
{
	if (x<=2) x = 60;
	if (x>900) x = 60;
	ex_timeoutx = x;
	return TRUE;
}
int ex_do_cmd(int p, char *cmdline, char *result, int bfsz, int *done)
{
	char *s;
	int r=FALSE;

	if (imsg==NULL) {
		strcpy(result,"-ERR ex_init was not called");
		*done = TRUE;
		return FALSE;
	}


	presult[p] = result;
	presultsz[p] = bfsz;
	backup_presult[p] = result;
	backup_presultsz[p] = bfsz;
	pdone[p] = done;
	ndone[p]++;
	if (lib_strlen(cmdline)>0) {
		s = strchr(cmdline,'\r');
		if (s!=NULL) {
			*s = '\n';
			s[1] = 0;
		}
		s = strchr(cmdline,'\n');
		if (s==NULL) strcat(cmdline,"\n");
		/*imsg("Sending command [%d] %.500s",p,cmdline);*/ /* This line logs paswords!  */
		r = spawn_write(pin[p],cmdline,lib_strlen(cmdline));
	}
	pstate[p] = P_BUSY;
	pstarted[p] = time(NULL);
	if (lib_strlen(cmdline)>0) {
		if (r<1) {
			if (imsg) imsg("ex_auth: spawn_write failed\n");
			ex_reset(p,"spawn write failed");
			return TRUE;
		}
	}
	p_tout[p] = time(NULL) + ex_timeoutx;
	return TRUE;
}
static int nresets;
int ex_nresets(void)
{
	return nresets;
}
int ex_poll(void)
{
	int n, i, r, now = (int)time(NULL);
	time_t tread;
	n = ex_n();
	ex_mutex_wait(__LINE__);
	mat = 1;
	for (i=0; i<n; i++) {
		mat = 2;
		if (pstate[i]!=P_BUSY) continue;
		mat = 3;
		if (now > p_tout[i])  {
			mat = 4;
			//if (imsg) imsg("ex_auth: spawn poll timeout %d seconds\n",ex_timeoutx);
			nresets++;
			mat = 5;
			ex_restart_one(i,"poll timeout");
			mat = 6;
			continue;
		}
		mat = 7;
		r = spawn_nready(pout[i]);
		mat = 8;
		if (r<0) {mat = 9; ex_restart_one(i,"poll timeout 2"); mat = 10; break;}
		else if (r==0) continue;
		mat = 11;
		tread = time(NULL);
		ex_read(i,r);
		//if (time(NULL)-tread>2) if (imsg) imsg("ex_auth: Delay reading from pipe %d seconds\n",time(NULL)-tread);
		mat = 12;
	}
	mat = 13;
	ex_mutex_release(__LINE__);
	return TRUE;
}
int ex_shutdown(void)
{
	return ex_shutdown_raw(FALSE);
}
int ex_set_shutdown(void)
{
	is_shutdown = TRUE;
	lib_spawn_freeenv(ex_env,ex_nenv);
	ex_nenv = 0;
	return TRUE;
}
int ex_shutdown_raw(int force)
{
	int n = ex_n();
	int p;
	char cmdline[BFSZ];
	int r;
	char *u=NULL;

	strcpy(cmdline,"exit\n");
	if (!force) ex_mutex_wait(__LINE__);
	for (p=0; p<n; p++) {
		if (pin[p] > 0) {
			r = spawn_write(pin[p],cmdline,lib_strlen(cmdline));
		}
	}
	lib_msleep(100); /* Let them shutdown nicely */
	for (p=0; p<n; p++) {
		if (pstate[p]==P_BUSY) {
			u = ex_user[p];
			if (u==NULL) u = "none";
			lprintf(presult[p],presultsz[p]-1,"-ERR %s External process failed shutdown\r\n",u);
			*pdone[p] = TRUE;
		}
		pstate[p] = P_READY;
		if (pout[p]>0) spawn_close_pipe(pout[p]); pout[p] = -1;
		if (pin[p]>0) spawn_close_pipe(pin[p]); pin[p] = -1;

		if (pid[p]!=0) spawn_kill(pid[p]); /* Kill the sucker */
		pid[p] = 0;
		if (pbf[p] != NULL) {
			free(pbf[p]); pbf[p] = NULL; npbf[p] = 0;
			backup_pbf[p] = NULL;
		}
	}
	if (!force) ex_mutex_release(__LINE__);
	return TRUE;
}
#define BIGBF 10000
int ex_check_params(int p, char *s)
{
	if (p<0 || p>=MAXEX) {
		emsg("Invalid p value %d\n",p);
		lib_abort();
	}
	if (s<pbf[p] || s>pbf[p]+BIGBF) {
		emsg("Invalid s is not pbf %p %p\n",s,pbf[p]);
		lib_abort();
	}
	if (backup_pbf[p]!=pbf[p]) {
		emsg("Invalid pbf %p %p\n",pbf[p],backup_pbf[p]);
		lib_abort();
	}
	if (backup_presult[p]!=presult[p]) {
		emsg("Invalid presult %p %p\n",presult[p],backup_presult[p]);
		lib_abort();
	}
	if (presult[p]==NULL) {
		emsg("Invalid presult2 %p %p\n",presult[p],backup_presult[p]);
		lib_abort();
	}
	if (backup_presultsz[p]!=presultsz[p]) {
		emsg("Invalid pbf %d %d\n",presultsz[p],backup_presultsz[p]);
		lib_abort();
	}
	return TRUE;
}
int ex_read(int p, int navail)
{
	int rest;
	int n;
	char *s,*s2;
	char lastline[BFSZ];
	if (navail>=BIGBF) navail = BIGBF-1;

	strcpy(lastline,"");
	if (pbf[p]==NULL) {
		backup_pbf[p] = pbf[p] = malloc(BIGBF+100);
	}
	mat = 100;
	rest = BIGBF - 100 - npbf[p];
	if (rest<=0) return FALSE;
	n = spawn_read_timed(pout[p],pbf[p]+npbf[p],rest,5000); // Small timeout as it should already be there.
	mat = 101;
	if (n<=0) return FALSE;
	mat = 102;

	s = pbf[p];
	if (n>=0) {
		s[n+npbf[p]] = 0;
		s[n+npbf[p]+1] = 0;
		s[n+npbf[p]+2] = 0;
	}

/*imsg("spawn_read %d (%s)\n",npbf[p],pbf[p]+npbf[p]);
imsg("spawn_read_has (%s)\n",s);
*/
	mat = 103;
	for (;*s!=0;) {
		char *was=s;
		s2 = strchr(s,'\n');
		mat = 104;
		if (s2==NULL) break;
		mat = 105;   // MUTEX HELD HERE WHEN CRASHED.
		/* RMH: this *would* remove bare \r's but chris wont let me! */
		if (s2!=s && s2[-1]=='\r') s2[-1]='\0';
		*s2 = 0;

		n = lib_strlen(s);
		mat = 506;   // NEW ONE
		//if (global_ex_logall && imsg) imsg("Authent[%d][response] %s\n",p,s);
		mat = 507;   // NEW ONE
//		if (presultsz[p] < n) emsg("WARNING: Authent response (%d bytes) too large for internal buffer (%d bytes), result will be truncated. Remove or block un-necessary fields with authent configuration file.",n,presultsz[p]);

		//if (pstate[p]==P_READY) axbort();
//imsg("ex_auth: Copying data into presult %d %p (%s) \n",p,presult[p],s);
		ex_check_params(p,s);
		mat = 508;   // NEW ONE
		lprintf(presult[p],presultsz[p]-1,"%s",s);
		// Reset timeout here for this channel. 
		p_tout[p] = time(NULL) + ex_timeoutx;
		if (strncmp(s,"-ERR",4)==0) {pstate[p] = P_READY; }
		else if (strncmp(s,"-DEAD",5)==0) {pstate[p] = P_READY; }
		else if (strncmp(s,"+OK",3)==0) {pstate[p] = P_READY; }
		else if (strncmp(s,"+DATA",5)==0) {
			if (pcb[p]!=NULL) {
				mat = 2508;   // added as I suspect it's in here it doesn't always return...
				(*pcb[p])(pcb_p1[p],s);  // YES, CONFIRMED IT'S IN THIS CALL IT LOCKS UP SOMETIMES... 
				mat = 2509;   // NEW ONE
		} 
		} else {
			if (imsg) imsg("Invalid data from authent module (%s) last line (%s)\n",s,lastline);
		}
		mat = 509;   // NEW ONE
		if (pstate[p] == P_READY) {
			if (pdone[p]!=NULL) *pdone[p] = TRUE;
			pdone[p] = NULL;
			pcb[p] = NULL;
		}
		mat = 510;   // NEW ONE
		ncpy(lastline,s,BFSZ-1);
		s = s2+1;
		if (pstate[p] == P_READY) {
			if (*s && imsg) imsg("ex_auth: P_READY state and more data ?? (%s)\n",s);
			if (*s && imsg) imsg("ex_auth: P_READY state and more data ?? was (%s)\n",was);
			break;
		}
	}
	mat = 109;
	if (*s==0) {
		npbf[p] = 0;
	} else {
		rest = lib_strlen(s);
		safe_memmove(pbf[p],s,rest+1,pbf[p],BIGBF);
		npbf[p] = rest;
		s = pbf[p];
	}
	mat = 110;
	return TRUE;
}
int ex_reset(int p, char *reason)
{
	char *u;
	if (imsg) imsg("ex_auth: reset(%d) %s\n",p,reason);
	// Mutex added by chrisp in surgemail 3.6 to fix fault where one thread would be doing a ex_read() when another set it to done. 

	// chrisp removing again because ex_reset is only called with the mutex. 
	//	ex_mutex_wait(__LINE__);
	if (pstate[p]==P_BUSY) {
		u = ex_user[p];
		if (u==NULL) u = "none";
//		if (emsg) emsg("ex_auth: reset() was busy, setting done (%s) %s\n",u,reason);
		lprintf(presult[p],presultsz[p]-1,"-ERR %s External process failed reset\r\n",u);
		if (pdone[p]!=NULL) *pdone[p] = TRUE;
		pdone[p] = NULL;
		pcb[p] = NULL;
	}
	pstate[p] = P_READY;
//	ex_mutex_release(__LINE__);
	if (pout[p]>0) spawn_close_pipe(pout[p]); pout[p] = -1;
	if (pin[p]>0) spawn_close_pipe(pin[p]); pin[p] = -1;
	if (pid[p]!=0) spawn_kill(pid[p]); pid[p] = 0; /* Kill the sucker */
//	if (imsg) imsg("ex_auth: Now try and recreate that child process %d\n",p);
#ifndef is_linux
	/* On linux we can't restart it except from master thread :-)
	SO MUST set it to busy
	*/
	ex_spawn(p);
#else
	pstate[p] = P_NONE; // So it isn't used until it's restarted.
#endif
	return TRUE;
}
int ex_check(char *user, char *pass, char *result, int bfsz)
{
	return ex_check_ip(user,pass,result,bfsz,"");
}
int ex_check_ip(char *user, char *pass, char *result, int bfsz, char *ip)
{
	char bf[BFSZ];
	char gotuser[BFSZ];
	char *s;
	int done=FALSE;
	int p,i;
	char xpass[BFSZ];
	time_t start = time(NULL);
	pass = ex_encode_spaces(xpass,pass);

	if (lib_strlen(user)>98) {
		lprintf(result,100,"-ERR invalid long username");
		return FALSE;
	}
	if (pass && (!pass[0] || strspn(pass," \t")==lib_strlen(pass))) {
		lprintf(result,100,"-ERR no password entered");
		return FALSE;
	}
	if (strchr(user,' ')!=NULL) {
		lprintf(result,100,"-ERR %.100s username cannot contain spaces",user);
		return FALSE;
	}
	if (broken) { // Chrisp changing to -DEAD so email retries
		lprintf(result,100,"-DEAD %.100s Spawn of authent process failed, examine log file and fix",user);
		if (emsg) emsg("Crashing because could not spawn authent process. Check g_authent_process points to valid binary etc...\n");
		lib_abort();
		return FALSE;
	}

	if (pass==NULL) {
		if (global_ex_logall && imsg) imsg("Authent[lookup %.100s]\n",user);
		sprintf(bf,"lookup %.100s",user);
	}
	else {
		char *xip="";
		if (ip!=NULL) xip = ip;
		if (global_ex_logall && imsg) imsg("Authent[check %.100s xxxx %.100s]\n",user,xip);
		sprintf(bf,"check %.100s %.100s",user,pass);
		if (ip && lib_strlen(ip)>0) sprintf(bf,"check %.100s %.100s %.100s",user,pass,ip);
	}

	p = ex_cmd(bf,result,bfsz,&done,NULL,NULL,user);
	for (i = 1;!done; i++) {
		/* RMH added polling log message, should appear once every 3 secs-ish */
		if ((i%30 == 0) && global_ex_logall && imsg) imsg("Authent[polling %d...]\n",p);
		ex_poll();
		if (!done) lib_msleep(20);
	}
	if (global_ex_logall && imsg) imsg("Authent[check %.100s] response: %s\n",user,result);
	if (time(NULL)-start>10) if (emsg) emsg("Authent: ex_check %s took %d seconds\n",user,time(NULL)-start);
	/**/
	s = strchr(result,' '); if (s==NULL) goto failed;
	ncpy(gotuser,s+1,BFSZ-1);
	s = strchr(gotuser,' '); if (s==NULL) goto failed;
	*s = 0;
	if (strcmpnc(gotuser,user)!=0) {
		if (imsg) imsg("ex_auth: Wrong user returned, trying again got (%s) wanted (%s)\n",gotuser,user);
failed:
		if (imsg) imsg("ex_auth: Bad response (%s)\n",result);
		ex_mutex_wait(__LINE__);
		ex_restart_one(p,"bad response");
		ex_mutex_release(__LINE__);
		return FALSE;
	}
	if (strncmp(result,"-DEAD",5)==0) {
		if (imsg) imsg("ex_auth: resetting (%s)\n",result);
		ex_mutex_wait(__LINE__);
		ex_restart_one(p,"dead response");
		ex_mutex_release(__LINE__);
	}
	if (result[0]=='+') return TRUE;
	return FALSE;
}
int ex_modify(char *userdom, char *var, char *val, char *sresult)
{  // nlib_user_set is probably better, not usre.
	/*
	This function seems to be broken.
	*/
	char result[BFSZ];
	char want[BFSZ];
	char *info;
	char *s;
	char *q;
	strcpy(sresult,"");
	if (!ex_lookup(userdom,"",result,BFSZ-10)) return FALSE;
	if (result[0]!='+') return FALSE;
	lprintf(want,BFSZ-1," %s=\"",var);
	s = strstr(result,want);
	if (s!=NULL) {
		*s = 0;
		s--;
		if (*s==' ') *s = 0; else s++;
		q = strchr(s+2,'"');
		if (q!=NULL) {
			q++;
			memmove(s,q,strlen(q)+1);
		}
	}
	lprintf(want,BFSZ-1," %s=\"%s\"",var,val);
	ncat(result,want,BFSZ-1);
	// +OK username config 0 REST
	info = strchr(result,' ');
	if (info!=NULL) info = strchr(info+1,' ');
	if (info!=NULL) info = strchr(info+1,' ');
	if (info!=NULL) info = strchr(info+1,' ');
	if (info!=NULL) {
		printf("info set (%s)\n",info);
		ex_set(userdom,"(NULL)",info,sresult,BFSZ-1);
		printf("result of set (%s)\n",sresult);
	}
	return TRUE;
}

int ex_modify_list(char *userdom, Pair *list, char *sresult, char *newpass)
{
	char result[BFSZ];
	char want[BFSZ];
	char *info;
	char *s,*s2;
	int want_len;
	Pair *p;
	strcpy(sresult,"");
	if (!ex_lookup(userdom,"",result,BFSZ-10)) {
		strcpy(sresult,"Account does not exist");
		return FALSE;
	}
	if (result[0]!='+') {
		ncpy(sresult,result,BFSZ-1);
		return FALSE;
	}

	for(p=list;p!=NULL;p=p->next) {
		int in_quote=FALSE;
		if (strchr(p->val,'\"')) continue; /* auth protocol does not allow " and it could cause security problems if users set these */
		if (strchr(p->var,'\"')) continue;
		if (strchr(p->var,' ')) continue;
		lprintf(want,BFSZ-1," %s=\"",p->var);
		want_len=lib_strlen(want);
		for(s=result;*s;s++) {
			if (*s=='\"') in_quote=!in_quote;
			if (!in_quote && strncmp(s,want,want_len)==0) {
				break;
			}
		}
		if (*s) {
			*s = 0;
			s2=strchr(s+want_len,'\"');
			if (s2) {
				memmove(s,s2+1,strlen(s2)+1);
			}
		}
		lprintf(want,BFSZ-1," %s=\"%s\"",p->var,p->val);
		ncat(result,want,BFSZ-1);
	}
	// +OK username config 0 REST
	info = strchr(result,' ');
	if (info!=NULL) info = strchr(info+1,' ');
	if (info!=NULL) info = strchr(info+1,' ');
	if (info!=NULL) info = strchr(info+1,' ');
	if (info!=NULL) {
		if (newpass==NULL) newpass = "(NULL)";
		ex_set(userdom,newpass,info+1,sresult,BFSZ-1);
	}
	return TRUE;
}
static int encode_spaces;
int ex_set_encode_spaces(int t)
{
	encode_spaces = t;
	return TRUE;
}
char *ex_encode_spaces(char *pass, char *in)
{
	char *s;
	char *out=pass;
	if (in==NULL) return NULL;
	ncpy(pass,in,BFSZ-1);
	if (encode_spaces) {
		for (s=in;*s!=0;s++) {
			if (out-pass>BFSZ-10) break;
			if (*s==' ') {*out++ = '\\'; *out++ = '_';}
			else if (*s=='\\') {*out++ = '\\'; *out++ = *s;}
			else *out++ = *s; 
		}
		*out++ = 0;
	}
	return pass;
}
int ex_set(char *user, char *pass, char *info, char *result, int bfsz)
{
	char *bf;
	int done=FALSE;
	char xpass[BFSZ];
	time_t start = time(NULL);
	pass = ex_encode_spaces(xpass,pass);
	/* allocate enough space + 10 extra, later fn's ncat a \n on to it */
	bf = malloc(lib_strlen(user)+lib_strlen(pass)+lib_strlen(info)+6+1+10);
	if (global_ex_logall && imsg) imsg("Authent[set %s xxxx %s]\n",user,info);
// debugging
//if (global_ex_logall && imsg) imsg("Authent[set %s %s %s]\n",user,pass,info);
	sprintf(bf,"set %s %s %s",user,pass,info); // safe because it's malloced.
	strcpy(result,"");
	if (strchr(pass,' ')!=NULL) {
		strcpy(result,"-ERR passwords cannot contain spaces sorry");
		return FALSE;
	}
//printf("ex_set, sending (%s)\n",bf);
	ex_cmd(bf,result,bfsz,&done,NULL,NULL,user);
	for (;!done;) {
		ex_poll();
		if (!done) lib_msleep(20);
	}
	free(bf);
	if (time(NULL)-start>10) if (emsg) emsg("Authent: ex_set %s took %d seconds\n",user,time(NULL)-start);
	if (result[0]=='+') return TRUE;
	return FALSE;
}
int ex_lookup(char *user, char *pass, char *result, int bfsz)
{
	char bf[BFSZ];
	int done=FALSE;
	char xpass[BFSZ];
	time_t start = time(NULL);
	pass = ex_encode_spaces(xpass,pass);

	if (strchr(user,' ')!=NULL) {
		lprintf(result,100,"-ERR %.100s username cannot contain spaces",user);
		return FALSE;
	}
	if (lib_strlen(user)>98) {
		lprintf(result,100,"-ERR invalid long username");
		return FALSE;
	}

	if (global_ex_logall && imsg) imsg("Authent[lookup %.100s]\n",user);
	sprintf(bf,"lookup %.100s",user);
	ex_cmd(bf,result,bfsz,&done,NULL,NULL,user);
	for (;!done;) {
		ex_poll();
		if (!done) lib_msleep(20);
	}
	if (global_ex_logall && imsg) imsg("Authent[lookup %.100s] response: %s\n",user,result);
	if (time(NULL)-start>10) if (emsg) emsg("Authent: ex_lookup %s took %d seconds\n",user,time(NULL)-start);
	if (result[0]=='+') return TRUE;
	return FALSE;
}
int ex_del(char *user, char *result, int bfsz)
{
	char bf[BFSZ];
	int done=FALSE;
	time_t start = time(NULL);

	if (global_ex_logall && imsg) imsg("Authent[del %.100s]\n",user);
	sprintf(bf,"del %.100s",user);
	ex_cmd(bf,result,bfsz,&done,NULL,NULL,user);
	for (;!done;) {
		ex_poll();
		if (!done) lib_msleep(20);
	}
	if (time(NULL)-start>10) if (emsg) emsg("Authent: ex_del %s took %d seconds\n",user,time(NULL)-start);
	if (result[0]=='+') return TRUE;
	return FALSE;
}
int ex_decode(char *data, char *field, char *result, char *rest, int bfsz)
{
	/* Pull out 'field' xxxx from 'data'
	+OK bob config 0 xxx="bbb" ...
	*/
	char *s,*s2;
	char tmp[BFSZ];
	char srch[BFSZ];
	int nomore=FALSE;
	int isquote = TRUE;

	strcpy(result,""); strcpy(rest,"");
	s = strchr(data,'\n'); if (s!=NULL) *s = 0;
	s = strchr(data,'\r'); if (s!=NULL) *s = 0;
	/* RMH-what is the point of this??
	s = strchr(data,' '); if (s==NULL) return FALSE;
	s = strchr(s+1,' '); if (s==NULL) return FALSE;
	s = strchr(s+1,' '); if (s==NULL) return FALSE;
	*/
	//s = strchr(s+1,' '); if (s==NULL) return FALSE;
	/* RMH-again, what is the point?
	data = s+1;*/
	sprintf(srch,"%s=\"",field);
	s = strstr(data,srch);
	if (s==NULL) {
		sprintf(srch,"%s=",field);
		s = strstr(data,srch);
		isquote = FALSE;
	}
	if (s==NULL) {
failed:		ncpy(rest,data,bfsz-1);
		ncpy(result,"",2);
		return FALSE;
	}
	/* xxx="bob"  bbb=fred  */
	*s = 0;
	ncpy(rest,data,bfsz-1);
	s += lib_strlen(srch);
	ncpy(tmp,s,BFSZ-1);
	if (isquote) s2 = strchr(tmp,'"');
	else {
		s2 = strchr(tmp,' ');
		if (s2==NULL) {s2 = tmp + lib_strlen(tmp); nomore = TRUE;}
	}
	if (s2==NULL) goto failed;
	*s2 = 0;
	if (!nomore) ncat(rest,s2+1,bfsz-1);
	ncpy(result,tmp,bfsz-1);

	return TRUE;
}
/* RMH - added to cause logging of authent lines */
int ex_logall(int state) {
	int oldstate = global_ex_logall;
	global_ex_logall = state;
	return oldstate;
}
/* RMH - added to stop weird ascii chars */
int ex_blockascii(int byes) {
	int olds = blockascii;
	blockascii = byes;
	return olds;
}
