/* lib_spawn		spawn and pipe routines for unix and nt */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#ifdef USE_POLL
#include <sys/poll.h>
#endif
#ifdef isunix
#include <unistd.h>
#endif

#ifdef WIN32
#	include <windows.h>
#	include <process.h>
#	include <io.h>
#	include <sys\stat.h>
#	include <direct.h>
#else
#	include <sys/types.h>
#	include <sys/wait.h>
#endif
#include "vsnprintf.h"

#ifndef USE_DFOPEN
#define real_fopen fopen
#define real_fclose fclose

#endif

void lib_abort_real(char *src, int line);
#define lib_abort() lib_abort_real(__FILE__,__LINE__)
int spawn_unlock_log(void);
int spawn_lock_log(void);
char *nullok(char *s);
int spawn_wait_freebsd(void);
int spawn_release_freebsd(void);


#define LIBSPAWN "lib_spawn: "
#ifdef WIN32
#define STDIN_FILENO  _fileno(stdin)
#define STDOUT_FILENO  _fileno(stdout)
char *spawn_mygetenv(char *s);
#endif

#ifdef unix
#define flushall() fflush(NULL)
#include <pwd.h>
#endif

#ifdef unix
#include <unistd.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#endif

#include "lib_spawn.h"
#include "lprintf.h"
#define DMID 8
#include "debug.h"
//#include "emsg.h"
void close_on_exec(int fd);

#ifndef false
#define false 0
#define true !false
#endif
#ifndef FALSE
#define FALSE 0
#define TRUE !FALSE
#endif
#define ncpy spawn_ncpy
char *ncpy(char *dst, char *src, int len);
int emsg(const char * arg_list, ...);
int  imsg(const char * arg_list, ...);
int dmsg(const char * arg_list, ...);
static int cur_line;

static int nlater;
typedef struct PLIST {int pid; struct PLIST *next; time_t when; } Plist;

#include "defines.h"
#define winmsg winmsgx
char *winmsg(int i, char *tmp);
char *lib_to_file_system_slash(char *txt);
int spawn_write(IHANDLE h, char *bf, int len);
void spawn_close_on_exec(int fd);
enum {PREAD,PWRITE};
char *mystrtok(char *start, char *toks, char **upto);
int lib_msleep(int x);
int spawn_mutex_wait(int line);
int spawn_mutex_release(void);
int lib_file_osize(FILE *f);
static FILE *lf;
void spawn_save_sk(int sk, int isopen);

int lib_file_osizeh_x(int handle, char *src, int line);
int lib_file_osize_x(FILE *f, char *src, int line);
#define lib_file_osizeh(a) lib_file_osizeh_x(a,__FILE__,__LINE__)
#define lib_file_osize(a) lib_file_osize_x(a,__FILE__,__LINE__)

static int (*open_hist)(IHANDLE handle, int isopen, int logonly, char *filename, int line, char *whence);

#ifndef SURGEMAIL
int smtp_info(char *s, int x)
{
	return TRUE;
}
#endif
int spawn_set_handle_cb(int (*cb)(IHANDLE handle, int isopen, int logonly, char *filename, int line, char *whence))
{
	open_hist  = cb;
	return TRUE;
}
char *spawn_lib_getcwd(char *tmp)
{
	strcpy(tmp,"none");
#ifdef WIN32
	GetCurrentDirectory(100,tmp);
#else
	getcwd(tmp,100);
#endif
	return tmp;
}

int spawn_lib_chdir(char *s) // setcwd set_cwd
{
#ifdef WIN32
/*	SetCurrentDirectory(s); */
	return chdir(s);
#else
	return chdir(s);
#endif
}

static int log_closes;
int spawn_log_closes(int v)
{
	log_closes = v;
	return TRUE;
}

static char sfname[BFSZ];
static int freebsd_doclose=TRUE;
int lib_spawn_noclose(int x)
{
	freebsd_doclose = !x;
	return TRUE;
}
int lib_spawn_logfile(char *fname)
{
	if (!spawn_lock_log()) return FALSE;
	if (lf!=NULL) fclose(lf);
	lf = fopen(fname,"a");
	strcpy(sfname,fname);
	spawn_unlock_log();
	return TRUE;
}
char *lib_emsg_time(char *s);
static int log_locked;
int spawn_unlock_log(void)
{
	if (!spawn_mutex_wait(__LINE__)) lib_abort();
	log_locked = FALSE;
	spawn_mutex_release();
	return TRUE;
}
int spawn_lock_log(void)
{
	time_t start = time(NULL);
	for (;TRUE;) {
		if (time(NULL)-start>60) return FALSE;
		if (!spawn_mutex_wait(__LINE__)) lib_abort();
		if (log_locked) {
			spawn_mutex_release();
			lib_msleep(20);
			continue;
		}
		log_locked = TRUE;
		spawn_mutex_release();
		break;
	}
	return TRUE;
}
int lib_spawn_logit(char *s)
{
	char tmp[BFSZ];
	if (!spawn_lock_log()) return FALSE;
	if (lf==NULL) {
		spawn_unlock_log();
		return TRUE;
	}
	fprintf(lf,"%s %s\n",lib_emsg_time(tmp),s);
//	fflush(lf);
	if (lib_file_osize(lf)>2000000) {
		fclose(lf); lf = NULL;
		remove(sfname);
		lf = fopen(sfname,"a");
	}
	spawn_unlock_log();
	return TRUE;
}
int lib_sp_write(IHANDLE f, char *bf){
	int count=(int)strlen(bf);

	return spawn_write(f, bf, count);
}

static int silent;
int spawn_silent(int x)
{
	silent = x;
	return TRUE;
}

void lib_spawn_logenv(char *env[])
{
	int i;	
	for(i = 0; env[i]; i++) imsg(LIBSPAWN"env(%s)\n",env[i]);
//for(i = 0; env[i]; i++) printf("env(%s)\n",env[i]);
	if (i == 0) imsg(LIBSPAWN"no env vars\n");
}

#ifdef unix
int spawn_close_pipex(IHANDLE h,char *src, int line)
{

	// removed logging as it can cause crash during mutex crash...
	int r;
	spawn_save_sk(h,FALSE);
//	if (log_closes) imsg("spawn_close fd=%d %s:%d\n",h,src,line);
	if (open_hist) open_hist(h,FALSE,FALSE,src,line,"PIPEX");
	r = close(h);
//	if (r!=0) emsg("spawn_close: failed (%d) %s\n",h,strerror(errno));
	return r==0;
}
int spawn_read(IHANDLE h, char *bf, int len)
{
	return read(h,bf,len);
}
#ifdef USE_POLL
int spawn_write_timed(IHANDLE h, char *bf, int len,int msec)
{
	int nc;
	struct pollfd ufds[10];
	if (h== -1) return 0; /* giveup immediately */
	ufds[0].fd = h;
	ufds[0].events = POLLOUT;
	ufds[0].revents = 0;
	errno = 0;
	nc = poll(ufds,1,msec);
	if (nc>0) {
		return spawn_write(h,bf,len);
	}
	return 0;
}
#else
int spawn_write_timed(IHANDLE h, char *bf, int len,int msec)
{
	fd_set readfd,writefd,exceptfd;
	int nc;
	struct timeval timeout;
	
	timeout.tv_sec = msec/1000; /*Wake up once a second*/
	timeout.tv_usec = (msec%1000) * 1000 ;
	FD_ZERO(&readfd);
	FD_ZERO(&writefd);
	FD_ZERO(&exceptfd);
	FD_SET(h,&writefd);
	errno = 0;
	nc = select(h+1,&readfd,&writefd,&exceptfd,&timeout);
	if (nc>0) {
		return spawn_write(h,bf,len);
	}
	return 0;
}
#endif
#ifdef USE_POLL
int spawn_read_timed(IHANDLE h, char *bf, int len,int msec)
{
	int nc;
	struct pollfd ufds[10];
	if (h == -1) return 0; /* giveup immediately */
	ufds[0].fd = h;
	ufds[0].events = POLLIN;
	ufds[0].revents = 0;
	errno = 0;
	nc = poll(ufds,1,msec);
	if (nc>0) {
		return spawn_read(h,bf,len);
	}
	return 0;
}
#else
int spawn_read_timed(IHANDLE h, char *bf, int len,int msec)
{
	fd_set readfd,writefd,exceptfd;
	int nc;
	struct timeval timeout;
	
	timeout.tv_sec = msec/1000; /*Wake up once a second*/
	timeout.tv_usec = (msec%1000) * 1000 ;
	FD_ZERO(&readfd);
	FD_ZERO(&writefd);
	FD_ZERO(&exceptfd);
	FD_SET(h,&readfd);
	errno = 0;
	nc = select(h+1,&readfd,&writefd,&exceptfd,&timeout);
	if (nc>0) {
		
		return spawn_read(h,bf,len);
	}
	return 0;
}
#endif
int spawn_write(IHANDLE h, char *bf, int len)
{
	return write(h,bf,len);
}
int spawn_quoted_token_split_rm_quot(char *s, char *p[], char *sep, int tmax)
{ /*  removes the quotes from the tokens  */
        int n=0;

        for (;*s!=0;) {
                s += strspn(s,sep);  /* Skip spaces */
                if (*s==0) break;
                p[n++] = s;
				if (*s == '\"') {
					p[n-1]++; s++; 
					while(*s!='\"' && *s != '\0') {
						s++;
					} 
					if (*s!='\"') {
						if (*s!=0) s++;
					} else {
						if (*s!=0) {*s='\0';s++;}
					}
				}
                s += strcspn(s,sep);  /* Find end of word */
                if (*s==0) break;
                *s = 0; s++;
                if ((n+2)>=tmax) {
                        break;
                }
        }
        p[n] = NULL;
        return n;
}

IHANDLE lib_spawn_user_getpid(char *cmd, char *env[], int uid, int gid, char *home, IHANDLE *xin, IHANDLE *xout, IHANDLE *xerr, char *msg, pid_t *ProcessID) // unix
{
	int r;
	int xread[2];
	int xwrite[2];
	int i;
	char bf[BFSZ];
	char xcmd[BFSZ];
	char *args[100];
	int nargs=0;
	char *upto=NULL;
	char *s;

	*xin = *xout = -1;
	strcpy(msg,"");
	(*ProcessID) = 0;
	if (!silent) imsg(LIBSPAWN"lib_spawn(%s)\n",cmd);
	//lib_spawn_logenv(env);

	// Chrisp added this mutex just to be paranoid, as sometimes pipe returns the same handles. 
	if (!spawn_mutex_wait(__LINE__)) lib_abort();
	if (pipe(xread)!=0) {
		strcpy(msg,strerror(errno)); 
		spawn_mutex_release();
		return FALSE;
	}
	if (pipe(xwrite)!=0) {
		strcpy(msg,strerror(errno)); 
		spawn_mutex_release();
		return FALSE;
	}
	spawn_mutex_release();

	if (open_hist) open_hist(xread[0],TRUE,FALSE,__FILE__,__LINE__,"user_getpid");  // Second handlle here. , and here.
	if (open_hist) open_hist(xwrite[1],TRUE,FALSE,__FILE__,__LINE__,"user_getpid");

	// These are teh two that get returned so need to be logged.
	if (open_hist) open_hist(xread[1],TRUE,FALSE,__FILE__,__LINE__,"user_getpid");  // duplicate here
	if (open_hist) open_hist(xwrite[0],TRUE,FALSE,__FILE__,__LINE__,"user_getpid");  // One handle here

	lib_spawn_logit(cmd);
	ncpy(xcmd,cmd,BFSZ-1);
	args[0] = NULL;
	args[1] = NULL;
	nargs = spawn_quoted_token_split_rm_quot(xcmd,args," \t\r\n", 96);
	args[nargs++] = NULL;
	args[nargs+1] = NULL;
	for (i=0;args[i]!= NULL;i++) {
		args[i] = strdup(args[i]);
	}

	*xin = (IHANDLE) xread[1];
	*xout = (IHANDLE) xwrite[0];
	if (xerr!=NULL) *xerr = (IHANDLE) -1;
//	spawn_close_on_exec(*xin);  Fixed below as we now close them in the child
//	spawn_close_on_exec(*xout);
	spawn_save_sk(*xin,TRUE);
	spawn_save_sk(*xout,TRUE);
	spawn_close_on_exec(fileno(stdin));
	spawn_close_on_exec(fileno(stdout));
	spawn_close_on_exec(fileno(stderr));

	spawn_wait_freebsd();
#ifdef USE_VFORK
	r = vfork();
#else
	r= fork();
#endif
#ifdef is_freebsd_bug
	if (r==0) {
		tcp_log_closes(FALSE);
		tcp_close_all();
	}
#endif 
	if (r==-1) {
		strcpy(msg,strerror(errno)); 
		emsg("lib_spawn: Fork failed (%s)\n",msg);	
	}
	if (r==0) { /* Child process */
		dup2(xread[0],STDIN_FILENO);
		close(xread[0]);
		close(xread[1]); /* Other end of it */
		dup2(xwrite[1],STDOUT_FILENO);
		close(xwrite[1]);
		close(xwrite[0]); /* Other end of it*/
		if (home!=NULL) {
			int r;
			r = spawn_lib_chdir(home);
			if (r!=0) {
				FILE *f=NULL;
				char bf[BFSZ];
				lprintf(bf,BFSZ-1,"chdir (%s) error %s",home,strerror(errno));
				if ((int)strlen(sfname)>0) f = real_fopen(sfname,"a");
				if (f!=NULL) {
					char tmp[BFSZ];
					fprintf(f,"%s, cwd=(%s)\n",bf,spawn_lib_getcwd(tmp));  // It didn't crash here. because home==NULL
					real_fclose(f);
				}
			}
		}
		if (uid>=0) {
			setreuid(uid,uid);
			setregid(gid,gid);
		}
		
		if (execve(args[0],args,env)<0) {
			FILE *f=NULL;
			// We use real_fopen as fopen crashed in dfopen once
			// I suspect because we are in a forked process so things aren't valid.
			if ((int)strlen(sfname)>0) f = real_fopen(sfname,"a");
			if (f!=NULL) {
				fprintf(f,"Error during spawn %s %s %s\n",nullok(args[0]),cmd,nullok(strerror(errno))); // can't be here since everything is tested for null...
				real_fclose(f);
			}
			_exit(0);
		}

		/* on success execl doesn't return */			;
//		printf("Panic shouldn't get here");
	}
	for (i=0; i<nargs; i++) {
		if (args[i]!=NULL) free(args[i]);
		args[i] = NULL;
	}
	if (log_closes) imsg("spawn_close2 fd=%d\n",xread[0]);
	if (log_closes) imsg("spawn_close2 fd=%d\n",xwrite[1]);
	if (open_hist) open_hist(xread[0],FALSE,FALSE,__FILE__,__LINE__,"user_getpid");
	if (open_hist) open_hist(xwrite[1],FALSE,FALSE,__FILE__,__LINE__,"user_getpid");

	close(xread[0]);
	close(xwrite[1]);
	// Now set them so they won't be shared by other spawned programs. 
	spawn_close_on_exec(*xin); 
	spawn_close_on_exec(*xout);

	(*ProcessID) = r;
	lprintf(bf,BFSZ-1,"Created process %d %s uid=%d gid=%d",r,cmd,uid,gid);
	lib_spawn_logit(bf);
	spawn_release_freebsd();

	return r;
}
#endif

#ifdef WIN32
int spawn_write_timed(IHANDLE h, char *bf, int len, int msec)
{
	char tmp[BFSZ];
	int rlen;
	if (!WriteFile((HANDLE) h,bf,len,&rlen,NULL)) {
		emsg(LIBSPAWN"Write failed  %s \n",winmsg(GetLastError(),tmp));
	}
	/*dmsg(LIBSPAWN"Writing data done h=%d %d (%s)\n",h,len,bf);   This Line Logs Passwords!*/
	dmsg(LIBSPAWN"Writing data done h=%d %d (%.8s...)\n",h,len,bf);
	return rlen;
}

int spawn_write(IHANDLE h, char *bf, int len)
{
	return spawn_write_timed(h,bf,len,1000000);
}




#define BUFSIZE 4096


BOOL CreateChildProcess(char *cmd, char *env);
VOID WriteToPipe(VOID);
VOID ReadFromPipe(VOID);
VOID ErrorExit(LPTSTR);
VOID ErrMsg(LPTSTR, BOOL);

int spawn_close_pipex(IHANDLE h, char *src, int line)
{
	CloseHandle((HANDLE) h);
	return TRUE;
}
int spawn_lib_isnt(void)
{
#ifdef WIN32
	int r;
	OSVERSIONINFO info;
	info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	r = GetVersionEx(&info);
	if (!r) {
		printf("getversionex failed %d\n",GetLastError());
		return FALSE;
	}
	/*printf("platform id = %d %d\n",info.dwPlatformId,VER_PLATFORM_WIN32_NT);*/
	if (info.dwPlatformId==VER_PLATFORM_WIN32_NT) return TRUE;
	return FALSE;
#else
	return FALSE;
#endif

}

#define INVALID_HANDLE NULL
int spawn_read_timed(IHANDLE h, char *bf, int len, int msec)
{
	char temp[BFSZ];
	int navail=0;
	int start = (int)time(NULL);
	int r=0;

	if (!spawn_lib_isnt()) imsg("windows 98 so just reading dircect\n");
	else {
		for (;TRUE;) {
			if (!PeekNamedPipe((HANDLE)h,temp,9,NULL,&navail,NULL)){
				imsg("lib_sp_bread: Peeknamedpipe failed, %s",winmsg(GetLastError(),temp));
				return -1;
			}
			if (navail>0) {
				dmsg("spawn_read_timed: auth, %d bytes available\n",navail);
				break;
			}
			if ((time(NULL)-start) > (msec/1000)) return 0;
			lib_msleep(20);
		}
	}

	{ // Never tried this, chrisp.
		COMMTIMEOUTS touts;
		if (GetCommTimeouts((HANDLE) h,&touts)!=0) {
			touts.ReadIntervalTimeout = msec/2;
			touts.ReadTotalTimeoutMultiplier = 0;
			touts.ReadTotalTimeoutConstant = msec;
			SetCommTimeouts((HANDLE) h,&touts);
		}
	}

	r = spawn_read(h,bf,len);
	dmsg("spawn_read_timed: auth, %d bytes read\n",r);
	return r;
}
int spawn_read(IHANDLE h, char *bf, int len)
{
	int rlen;
	char tmp[BFSZ];
	if (!ReadFile((HANDLE) h,bf,len,&rlen,NULL)) {
		if (!silent) imsg(LIBSPAWN"Read failed, %s \n",winmsg(GetLastError(),tmp));
		return -1;
	}
	bf[rlen]=0;
	return rlen;
}
int strncmpnc(const char *s1, const char *s2, size_t n);
int lib_spawn_cmd_isbat(char *cmd) {
	char *s;

	if (cmd[0] == '\"') s = strchr(cmd+1,'\"');
	else s = strchr(cmd,' ');
	if (!s) s = cmd+(int)strlen(cmd);
	s -= 4;
	if (s < cmd) s = cmd;
	return strncmpnc(s,".bat",4)==0;
}
IHANDLE lib_spawn_user_getpid(char *cmd, char *env[], int uid, int gid, char *home, IHANDLE *hin, IHANDLE *hout, IHANDLE  *herr, char *msg, pid_t *ProcessID) // windows
{
	HANDLE read1,write1,read2,write2,read3,write3,wwrite1;
	int er;
	char *s;
	char envblock[20000];
	int i,flags;
	char app[1000];
	char *xapp=NULL;
	STARTUPINFO st;
	PROCESS_INFORMATION p;
	SECURITY_ATTRIBUTES sec;
SECURITY_ATTRIBUTES sap,sat;

	*hin = *hout = (IHANDLE)-1;
	lib_spawn_logit(cmd);
	strcpy(msg,"");
	(*ProcessID) = 0;

	//lib_spawn_logenv(env);

	s = envblock;
	for (i=0;env[i]!=NULL;i++) {
		if ((int)strlen(env[i])==0) continue; 
		if (s-envblock + (int)strlen(env[i])>19000) break;
		strcpy(s,env[i]);
		s += (int)strlen(s) + 1;
	}
	*s++ = 0;
	*s++ = 0;
	sec.nLength = sizeof(sec);
	sec.lpSecurityDescriptor = NULL;
	sec.bInheritHandle = TRUE;
	GetStartupInfo(&st);
	CreatePipe(&read1,&write1,&sec,10000);
	CreatePipe(&read2,&write2,&sec,10000);
	CreatePipe(&read3,&write3,&sec,10000);
	st.hStdInput = read1;
	st.hStdOutput = write2;
	st.hStdError = write3;
	st.dwFlags = STARTF_USESTDHANDLES;

	/* Pull off the first parameter of cmd and add on
	the 'home' directory if it's not null */
	if (home!=NULL) {
		lprintf(app,400,"%s/%s",home,cmd);
		if (cmd[0] == '/') strncpy(app,cmd,200);
		if (cmd[0] == '\\') strncpy(app,cmd,200);
		if (cmd[1] == ':') strncpy(app,cmd,200);
		app[300] =0;
		s = strchr(app,' ');
		if (s!=NULL) *s = 0;
		xapp = app;
	}

	/* .bat files need different parameters */
	if (lib_spawn_cmd_isbat(cmd) ) {
		imsg(LIBSPAWN"CreateProcess starting a .bat file\n");
		st.dwFlags |= STARTF_USESHOWWINDOW;
		st.wShowWindow = SW_HIDE;
		flags = CREATE_NEW_CONSOLE;
	}
	else {
		imsg(LIBSPAWN"CreateProcess starting a normal file\n");
		flags = DETACHED_PROCESS;
	}

	/* Duplicate handle that is attached to child process's input handle, make it
	   non-inheritable, otherwise when we close it child will have am inherited copy
	   still open and it will not really close */
	if (DuplicateHandle(GetCurrentProcess(),write1,GetCurrentProcess(),&wwrite1,0,FALSE,DUPLICATE_SAME_ACCESS)) {
		CloseHandle(write1);
		write1 = wwrite1;
	}

	lib_to_file_system_slash(xapp); 
	lib_to_file_system_slash(cmd); 

	//flags = 0;

	//proc sec attributes
	sap.nLength=sizeof(SECURITY_ATTRIBUTES);
	sap.lpSecurityDescriptor=NULL;
	sap.bInheritHandle=1;

	//thread sec attributes
	sat.nLength=sizeof(SECURITY_ATTRIBUTES);
	sat.lpSecurityDescriptor=NULL;
	sat.bInheritHandle=1;
	//flags = 0;

	{
		char *block;
		for (block=envblock;TRUE;) {
			
			if ((int)strlen(block)==0) break;
			//printf("doblock %s\n",block);
			block = block + (int)strlen(block) + 1;
		}
	}

//printf("Home for createprocess is %s\n",home);
	if (CreateProcess(xapp,cmd
//		,NULL //security process attrib
//		,NULL // security thread attrib
   ,&sap
   ,&sat
		,TRUE // inherit handles
		,flags // flags
//		,NULL
		,envblock
		,home // working directory 
		,&st // startup info
		,&p // process information
		)) {
	} else {
		er = GetLastError();
		if (xapp==NULL) xapp = "NULLLL";
		emsg(LIBSPAWN"CreateProcess Failed  %d (%.90s)\n",er,xapp);
		if (er==ERROR_PATH_NOT_FOUND) strcpy(msg,"Path Not Found");
		if (er==ERROR_FILE_NOT_FOUND) strcpy(msg,"File Not Found");
		if (er==ERROR_ACCESS_DENIED) strcpy(msg,"Access Denied");
		CloseHandle(write1);
		CloseHandle(write2);
		CloseHandle(write3);
		CloseHandle(read3);
		CloseHandle(read2);
		CloseHandle(read1);
		emsg(LIBSPAWN"spawn (%s) (%s) %s\n",home,cmd,msg);
		return (IHANDLE)-1;
	}
	CloseHandle(p.hThread);
	CloseHandle(write2);
	CloseHandle(write3);
	CloseHandle(read1);
	*hin = (IHANDLE) write1;
	*hout = (IHANDLE) read2;
	if (herr!=NULL) {
		*herr = (IHANDLE) read3;
	} else {
		CloseHandle(read3);
	}
	(*ProcessID) = p.dwProcessId;
	return (IHANDLE) p.hProcess;
}
#endif

/*
WARNING: initialize excess to "" for FIRST CALL. line and excess MUST be same sized buffers.

FRIST CALL: reads a line into 'line', placing excess into 'excess'.
NEXT CALL : copies 'excess' into 'line', find a line OR reads more, placing excess into 'excess'.

RETURNS: TRUE on success, FALSE at eof, or on error, or if buffer is too small.
         sets 'read' to 0 on eof, -1 on error, x on success or buffer too small.
*/
int spawn_gets(IHANDLE h, char *line, char *excess, int bfsz, int *read) {
	int bytes,upto;
	char *p;

	/* this IS safe ALL the time */
	ncpy(line,excess,bfsz-1);

	if (read) *read = 0;

	do {
		p = strchr(line,'\n');
		if (p) {
			if (p>line && p[-1] == '\r') p[-1] = '\0';
			*p++ = '\0';
			if (read) *read = (int)(p-line);
			/* this IS safe ALL the time */
			ncpy(excess,p,bfsz-1);
			return TRUE;
		}
		upto = (int)strlen(line);
		if (read) *read = upto;

		/* Out of buffer space */
		if (upto == bfsz-1) {if (read) *read = -1; return FALSE;}

		bytes = spawn_read(h,&line[upto],bfsz-upto-1);
		if (bytes == 0 || bytes == -1) {
			if (read) *read = bytes;
			break;
		}
		line[upto+bytes] = '\0';
		if (read) *read += bytes;
	} while(TRUE);

	//RMH: wtf? the only way here is from the 'break' above where it's already set 'read' to the correct value
	//{if (read) *read = -1; return FALSE;}

	return FALSE;
}
char *ncpy(char *dst, char *src, int len)
{
        char *xdst = dst;
        int xlen = len;

        for (;(*src !=0) && (len>0); ) {*dst++  = *src++; len--;}
        *dst++ = 0;
        xdst[xlen] = 0;
        return xdst;
}

      
int spawn_gets_timed(IHANDLE h, char *line, char *excess, int bfsz, int *read, int timeout)
{
	int bytes,upto;
	char *p;

	/* this IS safe ALL the time */
	ncpy(line,excess,BFSZ-1);
	*read = 0;
	do {
		p = strchr(line,'\n');
		if (p) {
			if (p>line) if (p[-1] == '\r') p[-1] = '\0';
			*p++ = '\0';
			*read = (int)(p-line);
			ncpy(excess,p,BFSZ-1);
			return TRUE;
		}
		upto = (int)strlen(line);

		/* Out of buffer space */
		if (upto == bfsz-1) {*read = -1; return FALSE;}

		bytes = spawn_read_timed(h,&line[upto],bfsz-upto-1,timeout);
		if (bytes>=0) {
			line[upto+bytes] = '\0';
		}
		if (bytes == 0 || bytes == -1) {
			if (read) *read = bytes;
			break;
		}
	} while(TRUE);

	{*read = -1; return FALSE;}
}

IHANDLE lib_spawn_user(char *cmd, char *env[], int uid, int gid, char *home, IHANDLE *hin, IHANDLE*hout, char *msg){
	pid_t ProcessID;
	return lib_spawn_user_getpid(cmd, env, uid, gid, home, hin, hout, NULL,msg, &ProcessID);
}

#ifdef WIN32
char *winmsg(int i, char *bf)
{
	LPVOID lpMsgBuf;
	char *s;
	FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
		, NULL
		,i
		, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)
		, (LPTSTR) &lpMsgBuf, 0, NULL);

	strncpy(bf,lpMsgBuf,100); bf[100] = 0;
	LocalFree(lpMsgBuf);
	s = strchr(bf,'\n'); 	if (s!=NULL) *s = 0;
	s = strchr(bf,'\r'); 	if (s!=NULL) *s = 0;
	return bf;
}
#endif

#ifdef WIN32
int iswinnt(void)
{
	OSVERSIONINFO Ver;

	Ver.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
	if(!GetVersionEx(&Ver))
		return TRUE;
	if(Ver.dwPlatformId==VER_PLATFORM_WIN32_NT)
		return TRUE;
	return FALSE;
}
#endif


int spawn_kill_real(IHANDLE x, int fast);
static Plist *klist;

int spawn_kill_later(void)
{
#ifdef is_linux
	int was_line = cur_line;
	Plist *p,*pnext;
	time_t start = time(NULL);
	if (!spawn_mutex_wait(__LINE__)) lib_abort();
	if (time(NULL)-start>2) emsg("spawn_mutex took %d seconds to get, it was held by line %d\n",(int) (time(NULL)-start),was_line);
	for (p=klist;p!=NULL;p=pnext) {
		pnext = p->next;
		//emsg("spawn_kill_later (%d)\n",p->pid);  chrisp commented out, cannot see a reason to log this.  
		start = time(NULL);
		spawn_kill_real(p->pid,TRUE);
		//emsg("spawn_kill_later-done (%d) took %d seconds\n",p->pid,time(NULL)-start);
		free(p);
	}
	klist = NULL;
	spawn_mutex_release();
#endif
	return TRUE;
}
int spawn_kill(IHANDLE x)
{
	/* Linux threads are broken, the process must be killed from the master thread
	only so save in linked list and call later */
#ifdef is_linux
	Plist *p;

	/* Try and kill it anyway (incase we are in the right thread) */
	spawn_kill_real(x,FALSE);
	/* Then queue the request for the main smtp.c thread
	This will cause a (*very slow) memory leak if someone doesn't deal with it
	and call spawn_kill_later occasionally.

	*/
	p = calloc(1,sizeof(Plist));
	if (!spawn_mutex_wait(__LINE__)) lib_abort();
	p->next = klist;
	p->pid = x;
	p->when = time(NULL);
	klist = p;
	spawn_mutex_release();
	return TRUE;
#else
	return spawn_kill_real(x,FALSE);
#endif
}
int spawn_kill_real(IHANDLE x, int fast)
{
#ifdef unix
	if (!silent) imsg(LIBSPAWN"spawn_kill(pid=%d) killing process\n",x);
	if(spawn_isalive(x))
	{
		int ecode, i, y;

 		if (!silent) imsg(LIBSPAWN"killing slave process, handle %d\n", x);
		if(kill(x, SIGKILL)!=0)
			if (!silent) imsg(LIBSPAWN"*** Warning *** kill(%d) Failed, %s\n", x, strerror(errno));

		for(i=0;i<5;i++)
		{
			y=waitpid(x,&ecode,WNOHANG | WUNTRACED );
			if (y==x) break;
			if (y==0) if (!silent) imsg(LIBSPAWN"waitpid(%d) failed, this is normal (%s)\n",x,strerror(errno));
			if (fast) break;
			sleep(1);
		}
		if(i==5)
			if (!silent) imsg(LIBSPAWN"** Process %d resisted killing\n", x);
	} else if (!silent) imsg(LIBSPAWN"it was already dead\n");
	return TRUE;
#endif
#ifdef WIN32
	int i;
	HANDLE ph;
	DWORD permission;
	int code;
	char tmp[BFSZ];


	if (!silent) imsg(LIBSPAWN"spawn_kill(pid=%d) killing process\n",x);

	if(iswinnt())	permission=PROCESS_ALL_ACCESS;
	else		permission=PROCESS_QUERY_INFORMATION;

	ph = (HANDLE) x;


	if (!GetExitCodeProcess((HANDLE)ph, &code)) {
	    emsg(LIBSPAWN"GetExitCodeProcess(%d,%s)\n",x,winmsg(GetLastError(),tmp));
	}
	if (code==STILL_ACTIVE) {
		i=TerminateProcess(ph, 0);
		if(i==FALSE) {
			emsg(LIBSPAWN"TerminateProcess(%s)\n",winmsg(GetLastError(),tmp));
		}
	} else if (!silent) imsg(LIBSPAWN"the process has terminated normally %d\n");
	CloseHandle(ph);
	return TRUE;
#endif
}
int spawn_kill_h_deprecated_use_spawn_kill(IHANDLE x)
{
#ifdef unix
	return spawn_kill(x);
#endif
#ifdef WIN32
	int i;
	HANDLE ph = (HANDLE) x;
	DWORD permission;
	char tmp[BFSZ];


	if (!silent) imsg(LIBSPAWN"spawn_kill(h=%d) killing process\n",x);

	if(iswinnt())	permission=PROCESS_ALL_ACCESS;
	else		permission=PROCESS_QUERY_INFORMATION;
	i=TerminateProcess(ph, 0);
	if(i==FALSE) { emsg(LIBSPAWN"TerminateProcess(%s)\n",winmsg(GetLastError(),tmp)); return FALSE; }
	CloseHandle(ph);
	return TRUE;
#endif
}
#ifndef WIN32
static Plist *plist;
int spawn_release_later(IHANDLE pid)
{
	Plist *p;
#ifdef USE_DMALLOC
	p = calloc(1,sizeof(Plist));dmalloc_ignore_leak();
#else
	p = calloc(1,sizeof(Plist));
#endif
	p->next = plist;
	p->pid = pid;
	p->when = time(NULL);
	plist = p;
	return TRUE;
}
int spawn_release_later_do(void)
{
	int ecode;
	int r;
	int i;
	int y;
	int nl=0;
	Plist *p, **next,*x;
	next = &plist;
	
	for (p=plist;p!=NULL;p=x) {
		nl++;
		x = p->next;
		errno = 0;
		y=waitpid(p->pid,&ecode,WNOHANG | WUNTRACED  );
		// Chrisp change, after 1 hour we give up so we don't keep trying to release it...
		if ( (y==p->pid) || ((y<0) && (errno==ECHILD)) || (time(NULL)-p->when) > (60*60)) {
			*next = p->next;
			free(p);
			continue;
		}
		next = &p->next;
	}
	nlater = nl;
	return TRUE;
}
#endif
#ifdef DO_THREADS
#include "thread.h"
static Mutex *m_spawn;
#endif

void lib_spawn_free(void){
#ifdef DO_THREADS
	if (m_spawn!=NULL) th_mutex_free(m_spawn);
	m_spawn = NULL;
#endif
}
static int first_line,last_line;
static int cur_n;
#ifdef DO_THREADS
static o_thread cur_thid;
#else
static int cur_thid;
#endif
static time_t cur_start;
char *spawn_status(char *bf)
{
	lprintf(bf,BFSZ-1,"spawn_mutex at line %d first=%d last=%d n=%d thid=%d",cur_line,first_line,last_line,cur_n,(int) cur_thid);	
	return bf;
}


static int mutex_i;
static int (*mutex_cb)(int vm, int isget, int seconds);
int spawn_set_mutex(int (*cb)(int vm, int isget, int seconds), int m)
{
		mutex_i = m;
		mutex_cb = cb;
		return TRUE;
}


int spawn_mutex_wait(int line)
{
	if (mutex_cb) return mutex_cb(mutex_i,TRUE,600);
#ifdef DO_THREADS
	if (th_equal(cur_thid,th_oid())) goto gotit;
	if (m_spawn==NULL) m_spawn = th_mutex_new("spawn");
	if (!th_mutex_wait(m_spawn,60000)) {
		char bf[BFSZ];
		lprintf(bf,BFSZ-1,"lib_spawn_mutex, could not get, it was held at line %d first=%d last=%d n=%d thid=%d nlater=%d\n",cur_line,first_line,last_line,cur_n,(int) cur_thid,nlater);	
		if (!th_mutex_wait(m_spawn,10000)) {
			emsg("%s",bf);
			if (!th_mutex_wait(m_spawn,20000)) { // try a little bit harder...
				return FALSE;
			}
		}
	}
#endif
gotit:
	if (line!=cur_line) last_line = cur_line;
	cur_line = line;
	cur_start = time(NULL);
#ifdef DO_THREADS
	if (th_equal(cur_thid,th_oid())) {
		cur_n++;
	} else {
		cur_n = 1;
		first_line = line;
	}
	cur_thid = th_oid();
#endif

	return TRUE;
}
int spawn_wait_freebsd(void)
{
#ifdef is_freebsd_bug
	if (!spawn_mutex_wait(__LINE__)) lib_abort();
#endif
	return TRUE;
}
int spawn_release_freebsd(void)
{
#ifdef is_freebsd_bug
	spawn_mutex_release();
#endif
	return TRUE;
}
char *lib_startstop_logfile(void);
int spawn_mutex_release(void)
{
	if (mutex_cb) { mutex_cb(mutex_i,FALSE,600); return TRUE; }
#ifdef DO_THREADS
	cur_n--;
	if (cur_n>0) return TRUE;
	if (cur_n<=0) {
		cur_thid = 0;
		first_line = 0;
	}

	if (time(NULL)-cur_start > 10) {
		char bf[BFSZ];
		FILE *f;
		lprintf(bf,BFSZ-1,"lib_spawn: mutex held for %d seconds at line %d",(int) (time(NULL)-cur_start),cur_line); 
		th_mutex_release(m_spawn);
		//imsg("%s",bf);
			
		f = fopen(lib_startstop_logfile(),"a");
		if (f!=NULL) {
			fprintf(f,"%s\n",bf); // here is impossible... 
			fclose(f);
		}
		
	} else {
		th_mutex_release(m_spawn);
	}
#endif
	return TRUE;
}
int spawn_release(IHANDLE pid)
{
	int rcode;
	return spawn_release_timed(pid,0,&rcode);
}
int spawn_release_timed(IHANDLE pid, int t, int *rcode)
{
#ifdef unix
	int r,i,y,ecode=0;
	time_t start;
	char *sreason="None";
	*rcode = 0;
	if (!spawn_mutex_wait(__LINE__)) return TRUE;

	cur_line = __LINE__;
	spawn_release_later_do();
	cur_line = __LINE__;

	for(start = time(NULL); TRUE; ) {
		cur_line = __LINE__;
		y = waitpid(pid,&ecode,WNOHANG|WUNTRACED );
		cur_line = __LINE__;
		if (y==pid) {
			sreason = "waitpid worked";
			*rcode = WEXITSTATUS(ecode);
			break;
		}
		if (t <= time(NULL)-start) {
			cur_line = __LINE__;
			spawn_release_later(pid);
			cur_line = __LINE__;
			sreason = "waitpid queued for later";
			break;
		}
		cur_line = __LINE__;
		spawn_mutex_release();
		lib_msleep(100);
		if (!spawn_mutex_wait(__LINE__)) return TRUE;
		cur_line = __LINE__;
	}
	cur_line = __LINE__;
	spawn_mutex_release();
	imsg("spawn_release %s\n",sreason);
	return TRUE;
#else
	HANDLE ph;
	DWORD permission;
	int code;
	char tmp[BFSZ];

	*rcode = 0;
	if (!silent) imsg(LIBSPAWN"spawn_release(handle=%d) killing process\n",pid);

	if(iswinnt())	permission=PROCESS_ALL_ACCESS;
	else		permission=PROCESS_QUERY_INFORMATION;

	ph = (HANDLE) pid;


	if (!GetExitCodeProcess((HANDLE)ph, &code)) {
	    emsg(LIBSPAWN"GetExitCodeProcess(%d,%s)\n",pid,winmsg(GetLastError(),tmp));
	}
	if (code!=STILL_ACTIVE) *rcode = code;
	if (code==STILL_ACTIVE) {
		imsg(LIBSPAWN"spawn_release: Releasing handle for process still alive\n");
	} else if (!silent) imsg(LIBSPAWN"the process has terminated normally %d\n");
	if (CloseHandle(ph)==0) {
	    imsg(LIBSPAWN"CloseHandle(%d) %s\n",ph,winmsg(GetLastError(),tmp));
	}
#endif
	return TRUE;
}
int spawn_isalive(IHANDLE  x)
{
	int rcode;
	return spawn_isalive_rcode(x,&rcode);
}
int spawn_isalive_rcode(IHANDLE x, int *rcode)
{
#ifdef unix
	int ecode;
	int r;

	errno=0;
	*rcode = 0;
	if (x<=0) return FALSE;
	r=waitpid(x,&ecode,WNOHANG | WUNTRACED );
	*rcode =  WEXITSTATUS(ecode);
	if(r==x) return FALSE;
	if(errno==ECHILD) return FALSE;
	return TRUE;
#endif
#ifdef WIN32
	DWORD code;
	HANDLE ph;
	DWORD permission;
	char tmp[BFSZ];
	*rcode = 0;
	if (x== 0) return FALSE;
	if (x== (IHANDLE)-1) return FALSE;

	if(iswinnt())	permission=PROCESS_ALL_ACCESS;
	else		permission=PROCESS_QUERY_INFORMATION;
	ph = (HANDLE) x;
	if (!GetExitCodeProcess((HANDLE)ph, &code)) {
	    if (!silent) imsg(LIBSPAWN"GetExitCodeProcess(%d,%s)\n",x,winmsg(GetLastError(),tmp));
	}
	if (code==STILL_ACTIVE) return TRUE;
	*rcode = code;
	CloseHandle(ph);
	if (!silent) imsg(LIBSPAWN"Process not still active %d\n",x);
	return FALSE;
#endif
}


int spawn_nready(IHANDLE f)
{
	char temp[10];
	unsigned int navail=0;

#ifdef unix
	int err;
	err = ioctl(f,FIONREAD,&navail);
	return navail;
#endif
#ifdef WIN32
	if (!PeekNamedPipe((HANDLE)f,temp,9,NULL,&navail,NULL)){
		 emsg(LIBSPAWN"spawn_nready: Peeknamedpipe failed err %d\n",GetLastError());
		 return -1;
	 }
	return navail;
#endif
}

char *spawn_mygetenv(char *s)
{
	s = getenv(s);
	if (s==NULL) return "";
	return s;
}

#define addenv(xx) lprintf(bf,BFSZ-1,"%s=%s",xx,spawn_mygetenv(xx)); env[nenv++] = strdup(bf);

int lib_spawn_addenv_real(char *env[],int *nenv,char *bf)
{
	env[(*nenv)++] = strdup(bf);
	env[(*nenv)] = NULL;
	return TRUE;
}
int lib_spawn_addenv(char *env[],int *nenv,char *var, char *val)
{
	char bf[BFSZ];
	lprintf(bf,BFSZ-1,"%s=%s",var,val);
	return lib_spawn_addenv_real(env,nenv,bf);
}
int lib_spawn_freeenv(char *env[], int n)
{
	int i;
	for (i=0; i<n; i++) {
		free(env[i]);
		env[i] = NULL;
	}
	return TRUE;
}
int lib_spawn_loadenv(char *fname, char *env[], int maxn)
{
	char bf[BFSZ];
	char tmp[BFSZ];
	char *var,*val;
	int nenv = 0;
	FILE *f;

	f = fopen(fname,"r");
	if (!f) return nenv;

	while(nenv < maxn-1 && fgets(bf,BFSZ,f)) {
		var = strtok(bf,"\r\n");
		if (!var) continue;

		val = strchr(var,'=');
		if (val) *val++ = 0;
		else {
			val = getenv(var);
			if (!val) continue;
		}

		lprintf(tmp,BFSZ-1,"%s=%s",var,val);
		env[nenv++] = strdup(tmp);
	}
	fclose(f);

	env[nenv] = NULL;

	return nenv;
}
int lib_spawn_setenv(char *env[])
{
	char bf[BFSZ];
	int nenv = 0;
#ifdef WIN32
	addenv("Path");
	addenv("COMPUTERNAME");
	addenv("ComSpec");
	addenv("Cpu");
	addenv("HOMEDRIVE");
	addenv("HOMEPATH");
	addenv("NUMBER_OF_PROCESSORS");
	addenv("OS");
	addenv("Os2LibPath");
	addenv("PATHEXT");
	addenv("PROCESSOR_ARCHITECTURE");
	addenv("PROCESSOR_IDENTIFIER");
	addenv("PROCESSOR_LEVEL");
	addenv("PROCESSOR_REVISION");
	addenv("SystemDrive");
	addenv("SystemRoot");
	addenv("TEMP");
	addenv("TMP");
	addenv("USERDOMAIN");
	addenv("USERNAME");
	addenv("USERPROFILE");
	addenv("WINDIR");
#else
	addenv("PATH");
	addenv("LD_LIBRARY_PATH");
#endif
	env[nenv] = NULL;
	/*env[nenv+1] = NULL;*/
	return nenv;
}
IHANDLE lib_spawn_noenv_getpid(char *cmd, IHANDLE *hin, IHANDLE *hout, char *msg, pid_t *ProcessID)
{
	char *env[100];
	int nenv=0;	
	IHANDLE r;

	nenv = lib_spawn_setenv(env);
	if (nenv>99) lib_abort();
	r = lib_spawn_getpid(cmd,env,hin,hout,msg,ProcessID);
	lib_spawn_freeenv(env,nenv);
	return r;
}
IHANDLE lib_spawn_noenv(char *cmd, IHANDLE *hin, IHANDLE *hout,char *msg ){
	pid_t ProcessID;
	return lib_spawn_noenv_getpid(cmd, hin, hout, msg, &ProcessID);
}

void spawn_close_on_exec(int fd)
{
#ifdef isunix
        if (fcntl(fd,F_SETFD,1)==-1){
//                printf("Error doing close on exec fcntl %d %s\n",fd,strerror(errno));
        }
#endif
}
IHANDLE lib_spawn(char *cmd, char *env[], IHANDLE *xin, IHANDLE *xout, char *msg)
{
	return lib_spawn_user(cmd,env,-1,-1,NULL,xin,xout,msg);
}


IHANDLE lib_spawn_getpid(char *cmd, char *env[], IHANDLE *xin, IHANDLE *xout, char *msg, pid_t *ProcessID)
{
	return lib_spawn_user_getpid(cmd,env,-1,-1,NULL,xin,xout,NULL,msg, ProcessID);
}


#ifdef WIN32
#define my_environ _environ
#else
extern char **environ;
#define my_environ environ
#endif


#ifdef WIN32

int run_win32_process_and_wait(char *program, char *args)
/*
Runs the specified win32 process and waits for it to terminate before returning.
*/
{
	char cmd_line[BFSZ];
	STARTUPINFO StartupInfo = {0};
	int flags=DETACHED_PROCESS;	    
	PROCESS_INFORMATION ProcessInfo;

	StartupInfo.cb = sizeof( STARTUPINFO );
	lprintf(cmd_line,BFSZ-1,"%s %s",program,args);

	if ( CreateProcess( NULL, cmd_line, NULL, NULL, FALSE, flags, NULL, NULL, &StartupInfo, &ProcessInfo ) )
		{
		WaitForSingleObject( ProcessInfo.hProcess, INFINITE );
		CloseHandle(ProcessInfo.hProcess);
		CloseHandle(ProcessInfo.hThread);
		return true;
	}
	else {
		return false;
	}
}

int lib_detach_prog_bat(char *cmd, char *env[], char *msg, IHANDLE *phandle)
{
//	static DWORD len;
	int er;
	char *s;
	char envblock[20000];
	int i;
	STARTUPINFO st;
	PROCESS_INFORMATION p;
	SECURITY_ATTRIBUTES sec;

	lib_spawn_logit(cmd);
	strcpy(msg,"");
	s = envblock;
	for (i=0;env[i]!=NULL;i++) {
		if (s-envblock + (int)strlen(env[i])>19000) break;
		strcpy(s,env[i]);
		s += (int)strlen(s) + 1;
	}
	*s++ = 0;
	*s++ = 0;
	sec.nLength = sizeof(sec);
	sec.lpSecurityDescriptor = NULL;
	sec.bInheritHandle = FALSE;
	GetStartupInfo(&st);
	st.hStdInput = 0;
	st.hStdOutput = 0;
	st.hStdError = 0;
	st.dwFlags=STARTF_USESHOWWINDOW;
	st.wShowWindow=SW_HIDE;
	if (CreateProcess(NULL,cmd,NULL,NULL
		,TRUE,CREATE_NEW_CONSOLE
		,envblock,NULL
		,&st,&p)) {
	} else {
		er = GetLastError();
		lprintf(msg,BFSZ-1,"Error %d",er);
		if (er==ERROR_PATH_NOT_FOUND) strcpy(msg,"Path Not Found");
		if (er==ERROR_FILE_NOT_FOUND) strcpy(msg,"File Not Found");
		if (er==ERROR_ACCESS_DENIED) strcpy(msg,"Access Denied");
		return FALSE;
	}
	CloseHandle(p.hThread);
	if (phandle != NULL)
		*phandle = (IHANDLE) p.hProcess;
	return TRUE;
}

int lib_detach_prog_env(char *cmd, char *env[], char *msg, IHANDLE *phandle)
{
//	static DWORD len;
	int er;
	char *s;
	char envblock[20000];
	int i,flags;
	STARTUPINFO st;
	PROCESS_INFORMATION p;
	SECURITY_ATTRIBUTES sec;

	lib_spawn_logit(cmd);
	strcpy(msg,"");
	s = envblock;
	for (i=0;env[i]!=NULL;i++) {
		if (s-envblock + (int)strlen(env[i])>19000) break;
		strcpy(s,env[i]);
		s += (int)strlen(s) + 1;
	}
	*s++ = 0;
	*s++ = 0;
	sec.nLength = sizeof(sec);
	sec.lpSecurityDescriptor = NULL;
	sec.bInheritHandle = FALSE;
	GetStartupInfo(&st);
	st.hStdInput = 0;
	st.hStdOutput = 0;
	st.hStdError = 0;
	st.dwFlags = 0;

	/* .bat files need different parameters */
	if (lib_spawn_cmd_isbat(cmd)) {
		imsg(LIBSPAWN"CreateProcess starting a .bat file\n");
		st.dwFlags |= STARTF_USESHOWWINDOW;
		st.wShowWindow = SW_HIDE;
		flags = CREATE_NEW_CONSOLE;
	}
	else {
		imsg(LIBSPAWN"CreateProcess starting a normal file\n");
		flags = DETACHED_PROCESS;
	}

	if (CreateProcess(NULL,cmd,NULL,NULL
		,TRUE,flags
		,envblock,NULL
		,&st,&p)) {
	} else {
		er = GetLastError();
		lprintf(msg,BFSZ-1,"Error %d",er);
		if (er==ERROR_PATH_NOT_FOUND) strcpy(msg,"Path Not Found");
		if (er==ERROR_FILE_NOT_FOUND) strcpy(msg,"File Not Found");
		if (er==ERROR_ACCESS_DENIED) strcpy(msg,"Access Denied");
		return FALSE;
	}
	CloseHandle(p.hThread);
	if (phandle != NULL)
		*phandle = (IHANDLE) p.hProcess;
	return TRUE;
}
#else
#ifndef SOLARIS
//char *strdup(const char *s);
#endif
int lib_detach_prog_env(char *xcmd, char *env[], char *msg, IHANDLE *phandle)
{
	int r;
	char *args[100];
	char cmd[BFSZ];
	char *h;
	char *s;
	int i;
	int nargs = 0;
	char bf[BFSZ];
	if (!silent) imsg(LIBSPAWN"lib_spawn_detach(%s)\n",xcmd);
	ncpy(cmd,xcmd,BFSZ-1);
	lib_spawn_logit(cmd);

	/* arg parsing handles quoted args, quotes include ' " and ` */
	/*
	for(s = cmd; *s; s = h) {
		if (*s == '\'' || *s == '\"' || *s == '`') {
			h = strchr(s+1,*s);
			if (!h) {
				emsg(LIBSPAWN"Unbalanced quotes in command line");
				return FALSE;
			}
			h++;
		}
		else {
			for(h = s; *h && !sisspace(*h); h++) ;
		}
		if (*h == ' ') for(*h++ = '\0'; sisspace(*h); h++);
		args[nargs++] = strdup(s);
		imsg(LIBSPAWN"arg%d[%s]",nargs-1,args[nargs-1]);
	}
	*/
	for (s = mystrtok(cmd," ",&h);s!=NULL;) {
		if (nargs>30) break;
		args[nargs++] = strdup(s);
		s = mystrtok(NULL," ",&h);
	}

	args[nargs] = NULL;
	// removed by chris as suspects it locks up on solaris
	//fflush(NULL);
	spawn_wait_freebsd();
#ifdef USE_VFORK
	r = vfork();
#else
	r= fork();
#endif
#ifdef is_freebsd_bug
	if (r==0) {
		tcp_log_closes(FALSE);
		tcp_close_all();
	}
#endif 

	if (r==0) { /* Child process */
		if (execve(args[0],args,env)<0) {
//			printf(LIBSPAWN"Error on exec2 (%s) %s %s\n",args[0],cmd,strerror(errno));
			imsg(LIBSPAWN"Error on exec2 (%s) %s %s\n",args[0],cmd,strerror(errno));
			/*imsg(LIBSPAWN"Error on exec %s %s\n",cmd,strerror(errno));*/
			_exit(0);
		}
//		printf("Panic shouldn't get here");
	}
	for (i=0; i<nargs; i++) free(args[i]);
	*phandle = r;
	lprintf(bf,BFSZ-1,"Created process %d %s",r,cmd);
	lib_spawn_logit(bf);
	spawn_release_freebsd();

	return TRUE;
}
// this failed when I tried it on solaris, don't know why, authent modules seemed to kill it
int lib_spawnv_wait(char *xcmd, char *arg[], char *reason)
{
	int r;
	char cmd[BFSZ];
	char *h;
	char *s;
	char bf[BFSZ];
	int i;
	lib_spawn_logit(cmd);
	ncpy(cmd,xcmd,BFSZ-1);
	if (!silent) imsg(LIBSPAWN"lib_spawn_wait(%s)\n",cmd);
	// removed by chris as may lockup solaris
	//fflush(NULL);
	spawn_wait_freebsd();
#ifdef USE_VFORK
	r = vfork();
#else
	r= fork();
#endif
#ifdef is_freebsd_bug
	if (r==0) {
		tcp_log_closes(FALSE);
		tcp_close_all();
	}
#endif 

	if (r==0) { /* Child process */
		if (execv(arg[0],arg)<0) {
			lprintf(reason,BFSZ-1,"lib_spawn: Error on exec3 %s %s\n",cmd,strerror(errno));
			emsg(LIBSPAWN"lib_spawnv_wait (%s)\n",reason);
			_exit(0);
		}
	} else {
		lprintf(bf,BFSZ-1,"Created process wait %d %s",r,cmd);
		lib_spawn_logit(bf);
		waitpid(r,NULL,0);
	}
	spawn_release_freebsd();
	return r==0;
}


int lib_spawnv_wait_no_path(char *xcmd, char *arg[], char *reason, char *change_dir)
/*
Use instead of lib_spawnv_wait for standard unix command searching.
For example if you want to run "gunzip" but don't know the path
to the exectuable.
Returns process return value or -1 if it failed.
*/
{
	int r;
	char cmd[BFSZ];
	char *h;
	char *s;
	char bf[BFSZ];
	int i;
	int status;
	ncpy(cmd,xcmd,BFSZ-1);
	if (!silent) imsg(LIBSPAWN"lib_spawn_wait_no_path(%s)\n",cmd);
	lib_spawn_logit(cmd);
	// removed by chris as may lockup solaris
	//fflush(NULL);
	spawn_wait_freebsd();
#ifdef USE_VFORK
	r = vfork();
#else
	r= fork();
#endif
#ifdef is_freebsd_bug
	if (r==0) {
		tcp_log_closes(FALSE);
		tcp_close_all();
	}
#endif 

	if (r==0) { /* Child process */
		if (change_dir)
			chdir(change_dir);
		if (execvp(arg[0],arg)<0) {
			lprintf(reason,BFSZ-1,"lib_spawn: Error on exec3 %s %s\n",cmd,strerror(errno));
			_exit(-1);
		}
		status=-1;
	} else {
		spawn_release_freebsd();
		lprintf(bf,BFSZ-1,"Created process wait %d %s",r,cmd);
		lib_spawn_logit(bf);
		waitpid(r,&status,0);
		if (WIFEXITED(status)==0) {
			status=-1;
		}
		else {
			status=WEXITSTATUS(status);
			if (status==255) status=-1;
		}
	}
	return status;
}


#endif
int lib_detach_prog(char *xcmd, char *msg)
{
	int r;
	IHANDLE phandle;
	r = lib_detach_prog_env(xcmd,my_environ,msg,&phandle);
	if (r) {
		r = spawn_release(phandle);
	}
	return r;
}

int lib_detach_handle(char *xcmd, char *msg, IHANDLE *phandle)
{
	return lib_detach_prog_env(xcmd,my_environ,msg,phandle);
}
int lib_detach_handle_env(char *xcmd, char *msg, IHANDLE *phandle,char *env[])
{
	return lib_detach_prog_env(xcmd,env != NULL ? env : my_environ,msg,phandle);
}
int lib_detach_wait(IHANDLE x, int seconds, int *ret)
{
#ifdef unix
	int ecode=0;
	int y;
	int start=time(NULL);
	for(;time(NULL)-start<seconds;) {
		y=waitpid(x,&ecode,WNOHANG | WUNTRACED );
		if ( (y==x) || ((y<0) && (errno==ECHILD)) ) {
			*ret = WEXITSTATUS(ecode);
			return TRUE;
		}
		lib_msleep(20);
	}
	return FALSE;
#endif
#ifdef WIN32
	HANDLE ph;
	int code=0;
	time_t start=time(NULL);
	char tmp[BFSZ];

	ph = (HANDLE) x;
	for(;time(NULL)-start<seconds;) {
		if (!GetExitCodeProcess((HANDLE)ph, &code)) {
		    emsg(LIBSPAWN"GetExitCodeProcess(%d,%s)\n",x,winmsg(GetLastError(),tmp));
		    code = 0;
		}
		if (code!=STILL_ACTIVE) {
			*ret = code;
			return TRUE;
		}
		lib_msleep(20);
	}
	return FALSE;
#endif
}

int lib_detach_free(IHANDLE phandle)
{
	spawn_release(phandle);
	return TRUE;
}
int lib_detach_kill(IHANDLE phandle)
{
	// chrisp added a kill so the process is actually sent a kill signal...
	spawn_kill(phandle);
	spawn_release(phandle);
	return TRUE;
}

void spawn_save_sk(int sk, int isopen)
{
#ifdef is_freebsd_bug
	tcp_save_sk(sk,isopen);
#endif
}

char *spawn_error(char *bf) {
#ifdef WIN32
	winmsg(GetLastError(),bf);
#else
	ncpy(bf,strerror(errno),99);
#endif
	return bf;
}

int spawn_printf(IHANDLE h, char *format, ...)
{
	char bf[BFSZ];
	va_list args;
	int len;

	va_start(args,format);
	len = vsnprintf(bf,BFSZ,format,args);
	if (len < 0 || len >= BFSZ) bf[(len = BFSZ-1)] = 0;
	va_end(args);
	
	return spawn_write(h,bf,len);
}

int spawn_program_and_exit(char *cmd, char *param, char *reason)
{
	// replace current program with 'cmd' ...
#ifdef WIN32
	intptr_t ri;
	HINSTANCE r;
	r=ShellExecute(NULL,"open",cmd,param,"",SW_SHOW);
	ri=(intptr_t) r;
	if (ri<32) {		
		lprintf(reason,BFSZ-1,"Script (%s) failed r=%d",cmd,ri);
		return FALSE;
	}
#else
	char *args[10];
	args[0] = cmd;
	args[1] = param;
	args[2] = NULL;
	args[3] = NULL;
	if (execv(cmd,args)== -1) {
		lprintf(reason,BFSZ-1,"Script (%s) failed %s",cmd,strerror(errno));
		return FALSE;
	}
#endif
	exit(1);
	return TRUE;
}
