#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#ifdef WIN32
#include <windows.h>
#else
#include <signal.h>
#endif
#include "group.h"
#include "item.h"
#include "dnews.h"
#include "nntp.h"
#include "lib.h"
#include "memdebug.h"
#include "conf.h"
#include "user.h"
#include "ml.h"
#include "chan.h"
#undef mymalloc
#undef myfree
#define mymalloc(x) mymalloc2(x,M_USER)
#define myfree(x) myfree2(x,M_USER)

#ifndef LOGON32_LOGON_NETWORK 
#define LOGON32_LOGON_NETWORK 3
#endif
static char users_ip[BFSZ];
/*

Access.conf wants to lookup
        *:read,post:group=accounts::netwin.accounts:add

*/
int dnews_lookup_slow(char *xuser, char *pass, char *groups[], int *ngroups, int maxg);
int ufast_free(void);

int pam_lookup(char *user, char *pass, char *groups[], int *ngroups, int maxg, char *ip);
int radius_lookup(char *user, char *pass, char *groups[], int *ngroups, int maxg, int *mb);
int netware_lookup(char *user, char *pass);
int spawn_lookup(char *user, char *pass, char *groups[], int *ngroups, int maxg);
int external_lookup(char *user, char *pass, char *groups[], int *ngroups, int maxg);
int dnews_lookup(char *user, char *pass, char *groups[], int *ngroups, int max);
int bdb_lookup(char *user, char *pass, char *groups[], int *ngroups, int max);
int unix_groups(char *user, char *groups[],int *ngroups, int max);
int is_member(char *user, char *group);
int user_lookup(char *xuser, char *pass, char *groups[], int *ngroups, int max, char *ip, int skipcache, int *mb);
int ufast_lookup_fast(char *xuser, char *pass, char *groups[], int *ngroups, int maxg);
void spawn_exit(void);
int ufast_reload(void);
#ifdef TESTINGIT
int main(int argc, char *argv[])
{
        char *groups[100];
        int ngroups=100;
        int i;
        user_lookup(argv[1],argv[2],groups,&ngroups,"noip",FALSE);
        for (i=0; i<ngroups; i++) {
                printf("Group %s\n",groups[i]);
        }
        return 0;
}
#endif
int user_lookup(char *xuser, char *xpass, char *groups[], int *ngroups, int maxg, char *ip, int skipcache, int *mb)
{
#ifdef WIN32
        unsigned int i;
	int ltype;
        SID_NAME_USE sidtype;
        char bf[BFSZ];
	char user[BFSZ];
	char pass[BFSZ];
        int bflen;
        char dmy1[BFSZ];
        int dmy1len;
        int ng=*ngroups;
        HANDLE token;
        TOKEN_GROUPS *tg;
	char *s;
	char domains[BFSZ];
	char adomain[BFSZ];
        int len;
        int r;
	int nd;
        char *darray[100];
	int rr;
	int domain_login = FALSE;

	ncpy(user,xuser,100);
	ncpy(pass,xpass,BFSZ-30);
    
	if (strlen(config_get(C_PIPE_USE))!=0) skipcache = TRUE;
	if (config_true(C_AUTH_SPAWN_IP)) skipcache = TRUE;
	if (config_true(C_AUTH_SKIP_CACHE)) skipcache = TRUE;

	if (!skipcache) {
		imsg("Lookup user %s via Cache\n",user);
		if (user_cache_lookup(user,pass,groups,ngroups,maxg,mb)) return TRUE;
	}
	imsg("Lookup user via DNEWS database\n");
        if (dnews_lookup(user,pass,groups,ngroups,maxg))  return TRUE;
	imsg("External lookup\n");
        if (external_lookup(user,pass,groups,ngroups,maxg)) {return TRUE;}
        if (spawn_lookup(user,pass,groups,ngroups,maxg)) return TRUE;
	imsg("Radius lookup\n");
        if (radius_lookup(user,pass,groups,ngroups,maxg,mb)) return TRUE;
#ifdef HAS_PAM
        if (pam_lookup(user,pass,groups,ngroups,maxg,ip)) return TRUE;
#endif
        imsg("Lookup user via NT database\n");
        if (config_true(C_AUTH_NOSYSTEM)) return FALSE;
/*      if (strlen(config_get(C_AUTH_DLL)>0) {
                *ngroups = maxg;
                return auth_newdll(user,pass,groups,ngroups);
        }
*/

	/* If username contains domain, search c_nt_domain for
	that domain, and if we find it, define it as an additional group
	and do the lookup on the appropriate domain  */

	ncpy(domains,config_get(C_NT_DOMAIN),BFSZ-4);
	ncpy(adomain,config_get(C_NT_DOMAIN),BFSZ-4);
	s = strchr(user,'\\');
	if (s==NULL) s = strchr(user,'/');
	if (s!=NULL) {
		*s = 0;
		s++;
		ncpy(adomain,user,200);
		ncpy(user,s,200);
		nd = token_split(domains,darray,", \t",100);
		for (i=0; (int) i <nd; i++) {
			if (strcmpnc(adomain,darray[i])==0) {
				ncpy(adomain,darray[i],200);
				imsg("Domain lookup (%s) user (%s) \n",adomain,user);
				domain_login = TRUE;
				goto cont2;
			}
		}
		strcpy(adomain,config_get(C_NT_DOMAIN));
		strcpy(user,xuser);
		imsg("User supplied domain (%s) not found in dnews.conf list nt_domain (%s) \n",xuser,adomain);		
	} else {
		if (strlen(adomain)>0) {
			nd = token_split(domains,darray,", \t",100);
			if (nd>0) strcpy(adomain,darray[0]);	
			imsg("Using default domain (%s)\n",adomain);
		}
	}

cont2:
	ltype = LOGON32_LOGON_INTERACTIVE;
	if (config_true(C_NT_USER_BATCH)) ltype = LOGON32_LOGON_BATCH;
	if (config_true(C_NT_USER_NETWORK)) ltype = LOGON32_LOGON_NETWORK;

	r = LogonUser(user,adomain,pass,ltype,LOGON32_PROVIDER_DEFAULT,&token);
        if (r) imsg("User successfully logged in %s\n",user);
        else {
                rr = GetLastError();
                if (rr==ERROR_LOGON_TYPE_NOT_GRANTED) emsg("Logon type not granted, User must have Logon Locally access in their profile\n");
                else if (rr==ERROR_PRIVILEGE_NOT_HELD) emsg("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");
        }
        if (!r) {
                return FALSE;
        }

        r = GetTokenInformation(token,TokenGroups,NULL,0,&len);
        tg = calloc(1,len);
	if (tg==NULL) return TRUE;
        r = GetTokenInformation(token,TokenGroups,tg,len,&len);
        if (!r) {
                imsg("TokenInformation Lookup failed error = %d\n",GetLastError());
		free(tg);
                return TRUE;
        }
	ng = *ngroups;
        imsg("%d groups found, already got %d \n",tg->GroupCount,ng);
        for (i=0; i<tg->GroupCount; i++) {
                bflen = BFSZ-1;
                dmy1len = BFSZ-1;
                strcpy(bf,"");
                strcpy(dmy1,"");
                sidtype = 0;
                r = LookupAccountSid(NULL,(tg->Groups[i].Sid),bf,&bflen,dmy1,&dmy1len,&sidtype);
                if (!r) {
                        imsg("Lookup failed error = %d\n",GetLastError());
                        continue;
                }
                bf[bflen] = 0;
                imsg("User (%s) is member of (%s) \n",user,bf);
                groups[ng++] = mystrdup(bf,SD_USER);
                if (ng>=maxg) {
                        emsg("Groups array exhausted %d>=%d\n",ng,maxg);
                        break;
                }
        }
	if (domain_login) {
                groups[ng++] = mystrdup(adomain,SD_USER);		
	}
        *ngroups = ng;
        imsg("NT LOOKUP FINISHED, %d groups\n",ng);
        if (strlen(config_get(C_AUTH_GROUPFILE))>0) {
                unix_groups(user,groups,ngroups,maxg);
        }
        CloseHandle(token);     
	free(tg);
        return TRUE;
#else
	char user[BFSZ],pass[BFSZ];

	ncpy(user,xuser,100);
	ncpy(pass,xpass,BFSZ-20);

	if (strlen(config_get(C_PIPE_USE))!=0) skipcache = TRUE;
	if (config_true(C_AUTH_SPAWN_IP)) skipcache = TRUE;
	if (config_true(C_AUTH_SKIP_CACHE)) skipcache = TRUE;
	if (!skipcache) {
	        if (user_cache_lookup(user,pass,groups,ngroups,maxg,mb)) {
			imsg("Lookup user %s via Cache worked\n",user);
			return TRUE;
		}
	}
        if (dnews_lookup(user,pass,groups,ngroups,maxg)) {
		imsg("Lookup user %s via dnews_lookup(users.dat) worked\n",user);
		return TRUE;
	}
        if (bdb_lookup(user,pass,groups,ngroups,maxg)) {
		imsg("Lookup user %s via bdb lookup worked\n",user);
		return TRUE;
	}
        if (external_lookup(user,pass,groups,ngroups,maxg)) return TRUE;
	imsg("Lookup user %s via radius\n",user);
        if (radius_lookup(user,pass,groups,ngroups,maxg,mb)) return TRUE;
#ifdef HAS_PAM
        if (pam_lookup(user,pass,groups,ngroups,maxg,ip)) return TRUE;
#endif
        if (spawn_lookup(user,pass,groups,ngroups,maxg)) return TRUE;
        unix_groups(user,groups,ngroups,maxg);
        if (config_true(C_AUTH_NOSYSTEM)) return FALSE;
#ifdef NETWARE
	imsg("Trying netware lookup (%s)\n",user);
	if (netware_lookup(user,pass)) return TRUE;
#endif
        imsg("Lookup user via unix password database\n");
        return unix_lookup(user,pass);
#endif
} 
#ifdef NETWARE
#include <nwcntask.h>
#include <nwnet.h>

int netware_lookup(char *user, char *pass)
{
	NWDSContextHandle context;
	NWDSCCODE ccode;
	context = NWDSCreateContext();
	if ((long) context==ERR_CONTEXT_CREATION) {
		imsg("Error creating context\n");
		goto exit1;
	}
	ccode = NWDSLogin(context,0,user,pass,0);
	if (ccode) {
		imsg("NWDSLogin ccode: %d name=%s\n",ccode,user);
		goto exit2;
	}
	imsg("NWDSLogin succeeded for (%s)\n",user);
	NWDSLogout(context);

	NWDSFreeContext(context);
	return TRUE;
exit2:	NWDSFreeContext(context);
exit1:
	return FALSE;
/*	long con;
	int r;
	con = GetCurrentConnection();
	r = LoginObject(con,user,1,pass);
	if (r ==0) {
		imsg("Netware login succeeded for user (%s)\n",user);
		LogoutObject(con);	
	}
	imsg("Netware Login failed %d\n",r);
	return FALSE;
	*/
}
#endif
int unix_groups(char *user, char *groups[],int *ngroups,int maxg)
{
        /* Read defined group file and return list of groups this user is in.*/
        char *bf;
        char gname[BFSZ];
        char *s;
        FILE *f;
        int ng=*ngroups;
        if (strlen(config_get(C_AUTH_GROUPFILE))==0) return FALSE;
        f = fopen(config_get(C_AUTH_GROUPFILE),"r");
        if (f==NULL) {
                filemsg("Could not read %s\n",config_get(C_AUTH_GROUPFILE));
                return FALSE;
        }
        bf = mymalloc(100000);
        /* group:passwd:id:users,users,users */
        for (;!feof(f);) {
                if (ng>=maxg) break;
                if (fgets(bf,90000,f)==NULL) break;
                s = strchr(bf,':'); if (s==NULL) continue;
                *s = 0; ncpy(gname,bf,BFSZ-1);
                s = strchr(s+1,':'); if (s==NULL) continue;
                s = strchr(s+1,':'); if (s==NULL) continue;
                s++;
                s = strtok(s,",\r\n"); 
                for (;s!=NULL;) {
                        if (strcmp(user,s)==0) {groups[ng++] = mystrdup(gname,SD_USER); break;}
	                s = strtok(NULL,",\r\n"); 
                }
        }
        *ngroups = ng;  
        myfree(bf);
        return TRUE;
}
#define FILE_USERS file_config("users.dat")
/*user,xpass,access,wild,full,groups);*/
static int doneauth=FALSE;
void auth_setok(int x)
{
        doneauth = x;
}
int pass_test(char *p, char *pfile);
int users_idle2(void)
{
	static int xwas;
	/* This is the normal case, nothing to do ! */
	if (xwas!=lib_cdate(FILE_USERS)) {
		ufast_reload();
		xwas = lib_cdate(FILE_USERS);
	}
	return TRUE;
}
int users_idle(void)
{
	char users_dat[BFSZ];
	char users_old[BFSZ];
	char users_replace[BFSZ];
	char users_temp[BFSZ];

	strcpy(users_dat,FILE_USERS);
	strcpy(users_replace,file_config("users.replace"));
	strcpy(users_temp,file_config("users.temp"));
	strcpy(users_old,file_config("users.old"));

	if (lib_exists(users_temp)) {
		if (lib_filesize(users_temp)<=0) {
			emsg("Removing zero size %s file\n",users_temp);
			remove(users_temp); return TRUE;
		}
		emsg("Found old %s file, trying to use it again\n",users_temp);
	} else {
		if (lib_exists(users_replace)) {
			imsg("Found new users.replace file\n");
			if (rename(users_replace,users_temp)!=0) filemsg("users: Rename to %s failed\n",users_temp);
		} else {
			/* This is the normal case, nothing to do ! */
			return TRUE;
		}
	}
	if (!lib_exists(users_temp)) {
		emsg("The file %s disappeared\n",users_temp);
		return TRUE;
	}
	if (remove(users_old)!=0) {
		if (errno!=ENOENT) filemsg("Unable to remove (%s) \n",users_old);
	}
	if (rename(users_dat,users_old)!=0) {
		filemsg("users: Rename to %s failed, will try again shortly, probably it's being used just now\n",users_old);	
	}
	if (rename(users_temp,users_dat)!=0) {
		filemsg("users: Rename of %s failed, will try again shortly, probably it's being used just now\n",users_temp);	
	} else imsg("users: users.dat successfully updated\n");
	ufast_reload();
	return TRUE;
}
int users_touch(void)
{
        FILE *f;
        f = fopen(FILE_USERS,"r");
        if (f!=NULL) {fclosep(f); return TRUE;}
        f = fopen(FILE_USERS,"w");
        if (f!=NULL) fclosep(f);
        return TRUE;
}
int dnews_lookup(char *xuser, char *pass, char *groups[], int *ngroups, int maxg)
{
	if (config_true(C_USER_SLOW)) {
		return dnews_lookup_slow(xuser,pass,groups,ngroups,maxg);
	} else {
		return ufast_lookup_fast(xuser,pass,groups,ngroups,maxg);
	}
}
typedef struct UFAST {char *user; char *pass; char *groups; struct UFAST *next; } Ufast;
#define MAXU 4000
static Ufast *utable[MAXU];

int ufast_hash(char *s)
{
	return str_hash(s,MAXU);
}
int ufast_reload(void)
{
        FILE *f;
	int xline=0;
	int h;
	Ufast *u;
        char *s;
        char bf[BFSZ];
        char xbf[BFSZ];
        int n;
        char *p[100];

        f = fopen(FILE_USERS,"r");
        if (f==NULL) {
		if (errno!=ENOENT) filemsg("Error reading %s\n",FILE_USERS);
                return FALSE;
        }
	imsg("Users.dat has changed, reloading into memory\n");
	ufast_free();

        for (;!feof(f);) {
                if (fgets(bf,BFSZ-10,f)==NULL) break;
		xline++;
                strcpy(xbf,bf);
                s = strchr(xbf,'\n');
                if (s!=NULL) *s = 0;
                if (bf[0]=='#') continue;
                s = strchr(xbf,'\n'); if (s==NULL) strcat(bf,"\n");
                n = token_split2(bf,p,":",90);
                if (n<6) {imsg("Ignoring line %d not enough colons %s\n",xline,xbf); continue; }
		if (!config_true(C_AUTH_CASE)) strlwr(p[0]);

		h = ufast_hash(p[0]);
		u = mymalloc(sizeof(Ufast));
		memset(u,0,sizeof(Ufast));
		u->user = mystrdup(p[0],SD_USER);
		u->pass = mystrdup(p[1],SD_USER);
		u->groups = mystrdup(p[5],SD_USER);
		u->next = utable[h];
		utable[h] = u;
        }
        fclosep(f);
	return TRUE;
}
int ufast_free(void)
{
	Ufast *unext,*u;
	int i;
	for (i=0; i<MAXU; i++) {
		for (u=utable[i]; u!=NULL; u=unext) {
			unext = u->next;
			mystrfree(u->user,SD_USER);
			mystrfree(u->pass,SD_USER);
			mystrfree(u->groups,SD_USER);
			myfree(u);
		}
		utable[i] = NULL;
	}
	return TRUE;
}
int ufast_lookup_fast(char *xuser, char *pass, char *groups[], int *ngroups, int maxg)
{
	char user[BFSZ];
	char tmp[BFSZ];
	int gonly = FALSE;
	static int done;
        int ng=*ngroups;
	int h;
	char *s;
	Ufast *u;
	ncpy(user,xuser,200);
	if (!done) ufast_reload();
	done = TRUE;
	if (!config_true(C_AUTH_CASE)) strlwr(user);
	h = ufast_hash(user);
	
	for (u=utable[h]; u!=NULL; u=u->next) {
		if (strcmp(u->user,user)==0) {
			if (strcmp(u->pass,"*")==0) gonly = TRUE;
			if (strcmp(u->pass,"")==0) gonly = TRUE;
                        if (pass_test(pass,u->pass) || doneauth || gonly ) {
                                strcpy(tmp,u->groups);
                                s = strtok(tmp,"\n ,");
                                for (;s!=NULL;) {
                                        if (ng>=maxg) break;
                                        groups[ng++] = mystrdup(s,SD_USER);
                                        s = strtok(NULL,"\n ,");
                                }
                                *ngroups = ng;
				if (doneauth) return TRUE;
				if (pass_test(pass,u->pass)) return TRUE;
				if (gonly) {
					if (ng>0) imsg("user: (%.100s) users.dat %.100s ...\n",user,groups[0]);
					return FALSE;
				}
                                return TRUE;
			}

		}
	}
        *ngroups = ng;
        if (doneauth) return TRUE; 
        return FALSE;
}
int dnews_lookup_slow(char *xuser, char *pass, char *groups[], int *ngroups, int maxg)
{
        FILE *f;
        char *s;
        char tmp[BFSZ];
        char bf[BFSZ];
        char xbf[BFSZ];
	char user[BFSZ];
        int n;
	int xline = 0;
	int gonly = FALSE;
        int ng=*ngroups;
        char *p[100];

	ncpy(user,xuser,200);
        f = fopen(FILE_USERS,"r");
        if (f==NULL) {
		if (errno!=ENOENT) filemsg("Error reading %s\n",FILE_USERS);
                return FALSE;
        }
	if (!config_true(C_AUTH_CASE)) strlwr(user);
        for (;!feof(f);) {
                if (fgets(bf,BFSZ-10,f)==NULL) break;
		xline++;
                strcpy(xbf,bf);
                s = strchr(xbf,'\n');
                if (s!=NULL) *s = 0;
                if (bf[0]=='#') continue;
                s = strchr(xbf,'\n'); if (s==NULL) strcat(bf,"\n");
                n = token_split2(bf,p,":",90);

                if (n<6) {imsg("Ignoring line %d not enough colons %s\n",xline,xbf); continue; }
		if (!config_true(C_AUTH_CASE)) strlwr(p[0]);
                if (strcmp(user,p[0])==0) {
			if (strcmp(p[1],"*")==0) gonly = TRUE;
			if (strcmp(p[1],"")==0) gonly = TRUE;
                        if (pass_test(pass,p[1]) || doneauth || gonly ) {
                                fclosep(f);
                                strcpy(tmp,p[5]);
                                s = strtok(tmp,"\n ,");
                                for (;s!=NULL;) {
                                        if (ng>=maxg) break;
                                        groups[ng++] = mystrdup(s,SD_USER);
                                        s = strtok(NULL,"\n ,");
                                }
                                *ngroups = ng;
				if (doneauth) return TRUE;
				if (pass_test(pass,p[1])) return TRUE;
				if (gonly) {
					if (ng>0) imsg("user: (%.100s) users.dat %.100s ...\n",user,groups[0]);
					return FALSE;
				}
                                return TRUE;
                        }
                }
        }
        fclosep(f);
        *ngroups = ng;
        if (doneauth) return TRUE; 
        return FALSE;
}

int pass_hash(int n, char *s);
int pass_test(char *p, char *pfile)
{
        char bf[BFSZ];
        char *s;
        int key,hash;
        
	if (config_true(C_USER_BLANK_OK)) {
	        if (strlen(pfile)==0) return TRUE;
	} else {
	        if (strlen(pfile)==0) return FALSE;
	}
        if (strncmp(pfile,"$1",2)!=0) {
		if (!config_true(C_AUTH_CASE)) if (strcmpnc(p,pfile)==0) return TRUE;
                if (strcmp(p,pfile)==0) return TRUE;
                else return FALSE;
        }
        ncpy(bf,pfile+2,BFSZ-1);
        s = strchr(bf,'.');
        if (s==NULL) return FALSE;

        *s = 0;
        key = atoi(bf);
        hash = atoi(s+1);
        
        if (pass_hash(key,p)==hash) return TRUE;
        else return FALSE;
}
int pass_hash(int n, char *s)
{
        int total=0;
        for (;*s!=0;s++) {
                total += *s;
                total *= 64;
                total = total+n+total/3 % (1000000+*s + n);
                n = (n + total)/2;
                total = total % 1000000;
        }
        if (total<0) total = -total;
        return total;
}
char *pass_encrypt(char *s)
{
        int r,h;
        static char bf[BFSZ];
        srand(time(NULL)+ *s *4000);
        r = rand();
        if (r<0) r = -r;
        h = pass_hash(r,s);
        lprintf(bf,BFSZ-1,"$1%d.%d",r,h);
        return bf;
}

int external_lookup(char *user, char *pass, char *groups[], int *ngroups, int maxg)
{
        char *s;
        int ng=*ngroups;
        char usergrps[BFSZ2];


        if (!config_true(C_AUTH_EXTERN)) return FALSE;
        imsg("Looking up user (%s) via external routine\n",user);
        if (!dnewsauth(user,pass,usergrps,BFSZ2-1)) return FALSE;
        s = strtok(usergrps,"\n,");
        for (;s!=NULL;) {
                if (ng>=maxg) break;
                groups[ng++] = mystrdup(s,SD_USER);
                s = strtok(NULL,"\n,");
        }
        *ngroups = ng;
        return TRUE;
}
int bdb_check(char *user, char *pass, char *ugroups, int ugroups_size);
int bdb_lookup(char *user, char *pass, char *groups[], int *ngroups, int maxg)
{
#ifdef USER_BDB
        char *s;
        int ng=*ngroups;
        char usergrps[BFSZ2];


	if (strlen(config_get(C_AUTH_DBFILE))==0) return FALSE;

        imsg("Looking up user (%s) via Berkly DB file (%s)\n",user,config_get(C_AUTH_DBFILE));

	if (!bdb_check(user,pass,usergrps,BFSZ2-1)) return FALSE;
        s = strtok(usergrps,"\n,");
        for (;s!=NULL;) {
                if (ng>=maxg) break;
                groups[ng++] = mystrdup(s,SD_USER);
                s = strtok(NULL,"\n,");
        }
        *ngroups = ng;
        return TRUE;
#else
	return FALSE;
#endif
}
#ifdef USER_BDB
#include <sys/types.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include <db.h>
char *crypt(const char *key, const char *salt);
int bdb_check( char *user, char *pass, char *ugroups, int ugroups_size)
{
	char bf[BFSZ];
	DBT key,val;
	static DB *d;
	int r;
	char *dpass,*dgroups,*s;

	errno = 0;
	if (d==NULL) d = dbopen(config_get(C_AUTH_DBFILE),O_RDONLY,0640,DB_HASH,NULL);
	if (d==NULL) { 	filemsg("db open failed (%s)\n",config_get(C_AUTH_DBFILE)); 	return FALSE;	};
	key.data = user;
	key.size = strlen(key.data);
	r = d->get(d, &key,&val,0);
	if (r<0) filemsg("Key not found");
	if (r!=0) {
		imsg("User (%s) not found in db file\n",user);
		return FALSE;
	}
	if (val.size>BFSZ) val.size = BFSZ;
	if (val.size<0) val.size = 0;
	strncpy(bf,val.data,val.size); bf[val.size] = 0;

	dpass = bf;
	s = strchr(bf,':'); if (s==NULL) return FALSE;
	*s = 0; s++;
	dgroups = s;
	s = strchr(bf,':'); if (s!=NULL) *s = 0;
	ncpy(ugroups,dgroups,ugroups_size-1);
	
	/* Now check the password is ok */
	
	if (strcmp(crypt(pass, dpass), dpass)==0){
		return TRUE;
	}
	imsg("Password incorrect for user (%s)\n",user);
	return FALSE;

}
#endif

int user_doneauth(void)
{
        return doneauth;
}
#ifdef WIN32
#define SPAWN_AUTH 1
#endif
#ifdef isunix
#define SPAWN_AUTH 1
#endif

#ifdef SPAWN_AUTH
#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 "lib_spawn.h"
static int done_init;
static int pid;
static int pin,pout;
char *mygetenv(char *s);

/*
Extensions: if  auth_spawn_limits true, then sends

  check user pass ip.address ksent=nnn con=n
  
And will respond to:
  +ok user .... mb=nn con=nn speed=20000

*/

int spawn_lookup(char *user, char *pass, char *groups[], int *ngroups, int maxg)
{
        char *s;
        int ng=*ngroups;
        int r;
		char extraip[BFSZ];
	char extra[BFSZ];
	int i=0;
	char bf[BFSZ];
        char bf2[BFSZ];
	char bfx[BFSZ];
        char errmsg[BFSZ];
        static char *env[100];
	int nenv=0;
	int nib,con;
	int mb,sp;
	time_t start=0;
	int tout = atoi(config_get(C_AUTH_TIMEOUT));

	if (tout==0) tout = 30;

        if (strlen(config_get(C_AUTH_SPAWN))==0) return FALSE;

        imsg("Looking up user (%s) via spawned program (%s)\n",user,config_get(C_AUTH_SPAWN));
        env[0] = NULL;


        /*      Spawn process */
        if (!done_init) {
                done_init = TRUE;
#ifdef WIN32
#define addenv(xx) lprintf(bf,BFSZ-1,"%s=%s",xx,mygetenv(xx)); env[nenv++] = strdup(bf); 
		addenv("Path");
		addenv("COMPUTERNAME");	
		addenv("ComSpec");	
		addenv("Cpu");	
		addenv("HOMEDRIVE");	
		addenv("HOMEPATH");	
		addenv("OS");	
		addenv("Os2LibPath");	
		addenv("PATHEXT");	
		addenv("PRCOESSOR_ARCHITECTURE");	
		addenv("PRCOESSOR_IDENTIFIER");	
		addenv("PRCOESSOR_LEVEL");	
		addenv("PRCOESSOR_REVISION");	
		addenv("SystemDrive");	
		addenv("SystemRoot");	
		addenv("TEMP");	
		addenv("TMP");	
		addenv("WINDIR");	
		env[nenv] = NULL;
#endif

                pid = lib_spawn(config_get(C_AUTH_SPAWN), env, &pin, &pout, errmsg );
		imsg("spawn: Created process %d, %s\n",pid,config_get(C_AUTH_SPAWN));
                if (!pid) { filemsg("Spawn failed %s\n",config_get(C_AUTH_SPAWN)); return FALSE; }
        }
        /* Send cmdline */
	strcpy(extra,"");
	if (config_true(C_AUTH_SPAWN_LIMITS)) {
		ml_get_current(user,&nib,&con);
		lprintf(extra,100," ksent=%d con=%d",nib/10,con);
	}
	strcpy(extraip,"");
	if (config_true(C_AUTH_SPAWN_ADDIP)) {
		if (strlen(users_ip)>0) lprintf(extraip,100," %s",users_ip);
	}
	

        lprintf(bf,BFSZ-1,"check %s %s%s%s\n",user,pass,extraip,extra);
        lprintf(bfx,BFSZ-1,"check %s ***%s%s\n",user,extraip,extra);
	imsgf("spawn: Sent ->%.100s",bfx);
	dmsg("spawn_real: Sent ->%.100s",bf);
	strcpy(users_ip,"");
        lib_sp_write(pin,bf);

        /* Read data until +ok or -err arrives */
/*again:*/
	strcpy(bf,"");

	start = time(NULL);
#ifdef TRUE
	for (i=0; i<30000; i++) { /* wait for 30 seconds */
		if (i!=0) lib_msleep(10);
		r = lib_sp_bread(pout, bf, BFSZ-10 );/* non block read */
		/* 0 = no data yet 1 = got line -1 = channel closed, fatal error */
		if (r == -1) break;
		if (r ==1) goto gotit;
		if ((time(NULL)-start)>tout) break; 
	}	
#else
        r = lib_sp_read(pout, bf, BFSZ-10 );/* blocking read */
#endif
	if ( (r<0) || (strlen(bf)==0) || ((time(NULL)-start)>tout)  ) {
		if ((time(NULL)-start)>tout) emsg("spawn: No response for %d seconds, resetting spawn process\n",tout);
	        imsgf("spawn: response %s",bf);
		filemsg("spawn: Error with auth module, closing it\n");
		spawn_exit();
		return FALSE;
	}
gotit:
        imsgf("spawn: response %s",bf);
        /* if (strlen(bf)==0) goto again; */
        strlwr(bf);
        strcpy(bf2,"");
	if (strncmp(bf,"-dead",5)==0) {
		emsg("spawn: closing auth module %s\n",bf);
		spawn_exit();
		return FALSE;
	}
        if (bf[0]!='+') {
                emsg("auth_spawn: failed (%s)\n",bf);
                return FALSE;
        }

	mb = con = sp = -1;
        s = strstr(bf,"mb=");  if (s!=NULL) mb = atoi(s+3);
        s = strstr(bf,"con="); if (s!=NULL) con = atoi(s+4);
        s = strstr(bf,"speed="); if (s!=NULL) sp = atoi(s+6);	
	chan_setlimits(mb,con,sp);

        s = strstr(bf,"groups=\"");
        if (s!=NULL) {
                ncpy(bf2,s+8,BFSZ-10);
                s = strchr(bf2,'"');
                if (s==NULL) return FALSE;
                *s = 0;
        }

        s = strtok(bf2,"\n,");
        for (;s!=NULL;) {
                if (ng>=maxg) break;
                groups[ng++] = mystrdup(s,SD_USER);
                s = strtok(NULL,"\n,");
        }
        *ngroups = ng;
        return TRUE;
}
#ifdef WIN32
#define close _close
#endif
void spawn_exit(void)
{
        char bf[BFSZ];
	int i;
	int r;
        if (!done_init) return;

        /* Send quit */
        lib_sp_write(pin,"exit\n");

	/* Try and read response */
	for (i=0; i<100; i++) {
		if (i!=0) lib_msleep(10);
		r = lib_sp_bread(pout, bf, BFSZ-10 );/* non block read */
		if (r == -1) break;
		if (r ==1) break;
	}	
        
        if (pin) {close(pin); }
        if (pout) {close(pout); }
	done_init = FALSE;  
#ifdef isunix
        if (pid){
                int r;
                emsg("killing process %d", pid);
                kill(pid,SIGTERM);
                r=waitpid(pid, NULL, WUNTRACED);
                if (r != pid) emsg("waitpid returned %d for pid=%d", pid, r);
                pid = 0;
        }
#else
	if (pid!=0) spawn_kill_h(pid);
#endif
	pid = 0;
}
#else
void spawn_exit(void)
{
}
int spawn_lookup(char *user, char *pass, char *groups[], int *ngroups, int maxg)
{
        return FALSE;
}
#endif

typedef struct GLIST {char *gname; struct GLIST *next;} Glist;
typedef struct ULIST {char *user; char *pass; int mb; Glist *gfirst; } Ulist;
void user_cache_free_one(Ulist *u);
static Ulist *ucache;
static int ncache;
static int cupto;
int user_cache_init(void)
{
	if (ncache!=0) return TRUE;
	ncache = atoi(config_get(C_USER_CACHE));
	if (ncache==0) ncache = 1000;
	ucache = mymalloc(sizeof(Ulist) * ncache);
	memset(ucache,0,sizeof(Ulist) * ncache);
	return TRUE;
}
static int ignore_next;
int user_cache_lookup(char *user, char *pass, char *groups[], int *ngroups, int maxg, int *mb)
{
	Glist *g;
	int i,j;
	user_cache_init();
	for (i=0; i<ncache; i++) {
		if (ucache[i].user==NULL) continue;
		if (strcmpnc(user,ucache[i].user)==0) {
		 if ( (strcmp(pass,ucache[i].pass)==0) || (doneauth) ) {
		  for (g = ucache[i].gfirst, j=0; (j<maxg) && (g!=NULL); j++,g=g->next) {
			groups[j] = mystrdup(g->gname,SD_USER);				
			*ngroups = j+1;
		  }
		  *mb = ucache[i].mb;
		  imsg("Found user cache entry [%d]\n",i);
		  ignore_next = TRUE;
		  return TRUE;
		 }
		}
	}
	ignore_next = FALSE;
	return FALSE;
}
int user_cache_add(char * user, char *pass,char *groups[],int ngroups, int mb)
{
	int i,j;
	Glist *g;
	user_cache_init();
	if (ignore_next) {
		ignore_next = FALSE;
		return TRUE;
	}
	for (i=0; i<ncache; i++) {
		if (ucache[i].user==NULL) continue;
		if (strcmpnc(ucache[i].user,user)==0) break;
	}
	if (i>=ncache) i = cupto++;
	if (cupto>=ncache) cupto = 0;

	user_cache_free_one(&ucache[i]);
	
	ucache[i].user = mystrdup(user,SD_USER);
	ucache[i].pass = mystrdup(pass,SD_USER);
	ucache[i].mb = mb;

	imsg("Replacing user cache entry [%d] %s\n",i,user);
	for (j=0; j<ngroups; j++) {
		g = mymalloc(sizeof(Glist));
		g->next = ucache[i].gfirst;
		g->gname = mystrdup(groups[j],SD_USER);
		ucache[i].gfirst = g;
	}
	
	return TRUE;
	
}
int user_cache_free_single(char *user)
{
	int i;
	user_cache_init();
	for (i=0; i<ncache; i++) {
		if (ucache[i].user==NULL) continue;
		if (strcmpnc(user,ucache[i].user)==0) {
			user_cache_free_one(&ucache[i]);
			imsg("Freeing user_cache entry %d\n",i);
			return TRUE;
		}
	}
	return FALSE;

}
void user_cache_free_one(Ulist *u)
{
	Glist *g,*gnext;
	if (u->user!=NULL) mystrfree(u->user,SD_USER);
	if (u->pass!=NULL) mystrfree(u->pass,SD_USER);
	u->user = NULL;
	u->pass = NULL;
	for (g=u->gfirst; g!=NULL; g=gnext) {
		gnext = g->next;
		mystrfree(g->gname,SD_USER);
		myfree(g);
	}
	u->gfirst = NULL;
}
void user_cache_free(void)
{
	int i;
	user_cache_init();
	for (i=0; i<ncache; i++) {
		user_cache_free_one(&ucache[i]);
	}
}
int rad_lookup(char *user, char *pass, char *host, char *seckey, int ecode, char *extra);
#ifdef NORADIUS
int rad_lookup(char *user, char *pass, char *host, char *seckey, int ecode, char *extra)
{
	return FALSE;
}
#endif

int radius_lookup(char *user, char *pass, char *groups[], int *ngroups, int maxg, int *mb)
{
        char *s;
        int ng=*ngroups;
	char edata[2000];
	int ecode=26;
	int r;

        if (strlen(config_get(C_AUTH_RADIUS_HOST))==0) return FALSE;

        imsg("Looking up user (%s) via radius (%s)\n",user,config_get(C_AUTH_RADIUS_HOST));
	if (strlen(config_get(C_AUTH_RADIUS_SECRET))==0) {
		imsg("radius: Lookup failed, you must define auth_radius_secret in dnews.conf\n");
		return FALSE;
	}
        
	ecode = atoi(config_get(C_AUTH_RADIUS_ATTRIBUTE));
	if (ecode==0) ecode = 26;
	r = rad_lookup(user,pass,config_get(C_AUTH_RADIUS_HOST)
			,config_get(C_AUTH_RADIUS_SECRET)
			,ecode,edata);
	if (r) imsg("radius: Lookup succeeded for %s %s\n",user,edata);
	else {
		imsg("radius: Lookup failed for %s\n",user);
		return FALSE;
	}

	/* Read data until +ok or -err arrives */
        s = strtok(edata,"\n,");
        for (;s!=NULL;) {
                if (ng>=maxg) break;
                groups[ng++] = mystrdup(s,SD_USER);
		if (strncmp(s,"mb=",3)==0) {
			*mb = atoi(s+4);
		}
                s = strtok(NULL,"\n,");
        }
        *ngroups = ng;
        return TRUE;
}
#ifdef HAS_PAM
int pam_check(char *user, char *pass, char *pam_type, char *host, char *extra, char *etext);
int pam_lookup(char *user, char *pass, char *groups[], int *ngroups, int maxg, char *ip)
{
        char *s;
        int ng=*ngroups;
        char etext[BFSZ];
	char edata[2000];
	int ecode=26;
	int r;

        if (strlen(config_get(C_PAM_TYPE))==0) return FALSE;
	
        imsg("Looking up user (%s) via pam (%s)\n",user,config_get(C_PAM_TYPE));
	
	r = pam_check(user,pass,config_get(C_PAM_TYPE),ip,edata,etext);
	if (r) imsg("pam: Lookup succeeded for %s %s\n",user,edata);
	else {
		imsg("pam: Lookup failed for %s %s\n",user,etext);
		return FALSE;
	}

	/* Read data until +ok or -err arrives */
        s = strtok(edata,"\n,");
        for (;s!=NULL;) {
                if (ng>=maxg) break;
                groups[ng++] = mystrdup(s,SD_USER);
                s = strtok(NULL,"\n,");
        }
        *ngroups = ng;
        return TRUE;
}
#endif
int user_set_ipx(char *s)
{
	strcpy(users_ip,s);
	return TRUE;
}
