Skip to content
Snippets Groups Projects
userdat.c 88.5 KiB
Newer Older
/* Synchronet user data-related routines (exported) */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
#include "cmdshell.h"
#ifndef USHRT_MAX
	#define USHRT_MAX ((unsigned short)~0)

/* convenient space-saving global variables */
char* crlf="\r\n";
char* nulstr="";

static const char* strIpFilterExemptConfigFile = "ipfilter_exempt.cfg";

#define VALID_CFG(cfg)	(cfg!=NULL && cfg->size==sizeof(scfg_t))

/****************************************************************************/
rswindell's avatar
rswindell committed
/* Looks for a perfect match among all usernames (not deleted users)		*/
/* Makes dots and underscores synonymous with spaces for comparisons		*/
/* Returns the number of the perfect matched username or 0 if no match		*/
/****************************************************************************/
uint DLLCALL matchuser(scfg_t* cfg, const char *name, BOOL sysop_alias)
	int		file,c;
	char*	p;
	char	dat[LEN_ALIAS+2];
	char	str[256];
	if(!VALID_CFG(cfg) || name==NULL)
		return(0);

	if(sysop_alias &&
		(!stricmp(name,"SYSOP") || !stricmp(name,"POSTMASTER") || !stricmp(name,cfg->sys_id)))
	SAFEPRINTF(str,"%suser/name.dat",cfg->data_dir);
	if((stream=fnopen(&file,str,O_RDONLY))==NULL)
	for(l=0;l<length;l+=LEN_ALIAS+2) {
		fread(dat,sizeof(dat),1,stream);
		for(c=0;c<LEN_ALIAS;c++)
			if(dat[c]==ETX) break;
		dat[c]=0;
		if(!stricmp(dat,name)) 
			break;
		/* convert dots to spaces */
		if(!stricmp(str,name)) 
			break;
		/* convert spaces to dots */
		if(!stricmp(str,name)) 
			break;
		/* convert dots to underscores */
		if(!stricmp(str,name)) 
			break;
		/* convert underscores to dots */
		strcpy(str,dat);
		if(!stricmp(str,name)) 
			break;
		/* convert spaces to underscores */
		strcpy(str,dat);
		if(!stricmp(str,name)) 
			break;
		/* convert underscores to spaces */
		strip_space(dat,str);
		strip_space(name,tmp);
		if(!stricmp(str,tmp)) 
	if(l<length)
		return((l/(LEN_ALIAS+2))+1); 
/****************************************************************************/
uint DLLCALL total_users(scfg_t* cfg)
{
    char	str[MAX_PATH+1];
    uint	total_users=0;
	int		file;
    long	l,length;

	if(!VALID_CFG(cfg))
		return(0);

	if((file=openuserdat(cfg, /* for_modify: */FALSE)) < 0)
	for(l=0;l<length;l+=U_LEN) {
		lseek(file,l+U_MISC,SEEK_SET);
		if(read(file,str,8)!=8)
			continue;
		getrec(str,0,8,str);
		if(ahtoul(str)&(DELETED|INACTIVE))
			continue;
		total_users++;
	}
	close(file);
	return(total_users);
}


/****************************************************************************/
/* Returns the number of the last user in user.dat (deleted ones too)		*/
/****************************************************************************/
{
	char str[256];
	long length;

	if(!VALID_CFG(cfg))
		return(0);

	SAFEPRINTF(str,"%suser/user.dat", cfg->data_dir);
		return((uint)(length/U_LEN));
	return(0);
}

/****************************************************************************/
/* Deletes (completely removes) last user record in user.dat				*/
/****************************************************************************/
BOOL DLLCALL del_lastuser(scfg_t* cfg)
{
	int		file;
	long	length;

	if(!VALID_CFG(cfg))
		return(FALSE);

	if((file=openuserdat(cfg, /* for_modify: */TRUE)) < 0)
		return(FALSE);
	if(length<U_LEN) {
		close(file);
		return(FALSE);
	}
	chsize(file,length-U_LEN);
	close(file);
	return(TRUE);
}

/****************************************************************************/
/* Opens the user database returning the file descriptor or -1 on error		*/
/****************************************************************************/
int DLLCALL openuserdat(scfg_t* cfg, BOOL for_modify)
	char path[MAX_PATH+1];
	if(!VALID_CFG(cfg))
		return(-1); 
	SAFEPRINTF(path,"%suser/user.dat",cfg->data_dir);
	return nopen(path, for_modify ? (O_RDWR|O_CREAT|O_DENYNONE) : (O_RDONLY|O_DENYNONE)); 
int DLLCALL closeuserdat(int file)
{
	return close(file);
}

/****************************************************************************/
/* Locks and reads a single user record from an open user.dat file into a	*/
/* buffer of U_LEN+1 in size.												*/
/* Returns 0 on success.													*/
/****************************************************************************/
int DLLCALL readuserdat(scfg_t* cfg, unsigned user_number, char* userdat, int infile)
{
	int i,file;
		file = infile;
	else {
		if((file = openuserdat(cfg, /* for_modify: */FALSE)) < 0)
			return file;
	}

	if(user_number > (unsigned)(filelength(file)/U_LEN)) {
		if(file != infile)
			close(file);
	lseek(file,(long)((long)(user_number-1)*U_LEN),SEEK_SET);
	i=0;
	while(i<LOOP_NODEDAB
		&& lock(file,(long)((long)(user_number-1)*U_LEN),U_LEN)==-1) {
		i++; 
	}
	if(i>=LOOP_NODEDAB) {
		if(file != infile)
			close(file);
		return(-2); 
	}

	if(read(file,userdat,U_LEN)!=U_LEN) {
		unlock(file,(long)((long)(user_number-1)*U_LEN),U_LEN);
		if(file != infile)
			close(file);
	unlock(file,(long)((long)(user_number-1)*U_LEN),U_LEN);
	if(file != infile)
		close(file);
	return 0;
}

/****************************************************************************/
/* Fills the structure 'user' with info for user.number	from userdat		*/
/* (a buffer representing a single user 'record' from the user.dat file		*/
/****************************************************************************/
int DLLCALL parseuserdat(scfg_t* cfg, char *userdat, user_t *user)
{
	char str[U_LEN+1];
	int i;
	unsigned user_number;

	if(user==NULL)
		return(-1);
	user_number=user->number;
	memset(user,0,sizeof(user_t));

	if(!VALID_CFG(cfg) || user_number < 1)
		return(-1); 
	
	/* The user number needs to be set here
	   before calling chk_ar() below for user-number comparisons in AR strings to function correctly */
	user->number=user_number;	/* Signal of success */

	/* order of these function calls is irrelevant */
	getrec(userdat,U_ALIAS,LEN_ALIAS,user->alias);
	getrec(userdat,U_NAME,LEN_NAME,user->name);
	getrec(userdat,U_HANDLE,LEN_HANDLE,user->handle);
	getrec(userdat,U_NOTE,LEN_NOTE,user->note);
	getrec(userdat,U_COMP,LEN_COMP,user->comp);
	getrec(userdat,U_COMMENT,LEN_COMMENT,user->comment);
	getrec(userdat,U_NETMAIL,LEN_NETMAIL,user->netmail);
	getrec(userdat,U_ADDRESS,LEN_ADDRESS,user->address);
	getrec(userdat,U_LOCATION,LEN_LOCATION,user->location);
	getrec(userdat,U_ZIPCODE,LEN_ZIPCODE,user->zipcode);
	getrec(userdat,U_PASS,LEN_PASS,user->pass);
	getrec(userdat,U_PHONE,LEN_PHONE,user->phone);
	getrec(userdat,U_BIRTH,LEN_BIRTH,user->birth);
	getrec(userdat,U_MODEM,LEN_MODEM,user->modem);
deuce's avatar
deuce committed
	getrec(userdat,U_IPADDR,LEN_IPADDR,user->ipaddr);
	getrec(userdat,U_LASTON,8,str); user->laston=ahtoul(str);
	getrec(userdat,U_FIRSTON,8,str); user->firston=ahtoul(str);
	getrec(userdat,U_EXPIRE,8,str); user->expire=ahtoul(str);
	getrec(userdat,U_PWMOD,8,str); user->pwmod=ahtoul(str);
	getrec(userdat,U_NS_TIME,8,str);
	user->ns_time=ahtoul(str);
	if(user->ns_time<0x20000000L)
		user->ns_time=user->laston;  /* Fix for v2.00->v2.10 */
	getrec(userdat,U_LOGONTIME,8,str); user->logontime=ahtoul(str);

	getrec(userdat,U_LOGONS,5,str); user->logons=atoi(str);
	getrec(userdat,U_LTODAY,5,str); user->ltoday=atoi(str);
	getrec(userdat,U_TIMEON,5,str); user->timeon=atoi(str);
	getrec(userdat,U_TEXTRA,5,str); user->textra=atoi(str);
	getrec(userdat,U_TTODAY,5,str); user->ttoday=atoi(str);
	getrec(userdat,U_TLAST,5,str); user->tlast=atoi(str);
	getrec(userdat,U_POSTS,5,str); user->posts=atoi(str);
	getrec(userdat,U_EMAILS,5,str); user->emails=atoi(str);
	getrec(userdat,U_FBACKS,5,str); user->fbacks=atoi(str);
	getrec(userdat,U_ETODAY,5,str); user->etoday=atoi(str);
	getrec(userdat,U_PTODAY,5,str); user->ptoday=atoi(str);
	getrec(userdat,U_ULB,10,str); user->ulb=atol(str);
	getrec(userdat,U_ULS,5,str); user->uls=atoi(str);
	getrec(userdat,U_DLB,10,str); user->dlb=atol(str);
	getrec(userdat,U_DLS,5,str); user->dls=atoi(str);
	getrec(userdat,U_CDT,10,str); user->cdt=atol(str);
	getrec(userdat,U_MIN,10,str); user->min=atol(str);
	getrec(userdat,U_LEVEL,2,str); user->level=atoi(str);
	getrec(userdat,U_FLAGS1,8,str); user->flags1=ahtoul(str);
	getrec(userdat,U_FLAGS2,8,str); user->flags2=ahtoul(str);
	getrec(userdat,U_FLAGS3,8,str); user->flags3=ahtoul(str);
	getrec(userdat,U_FLAGS4,8,str); user->flags4=ahtoul(str);
	getrec(userdat,U_EXEMPT,8,str); user->exempt=ahtoul(str);
	getrec(userdat,U_REST,8,str); user->rest=ahtoul(str);
	getrec(userdat,U_ROWS,2,str); user->rows=atoi(str);
	if(user->rows && user->rows<10)
		user->rows=10;
	user->sex=userdat[U_SEX];
	if(!user->sex)
		user->sex=' ';	 /* fix for v1b04 that could save as 0 */
	user->prot=userdat[U_PROT];
	if(user->prot<' ')
		user->prot=' ';
	getrec(userdat,U_MISC,8,str); user->misc=ahtoul(str);
	if(user->rest&FLAG('Q'))
		user->misc&=~SPIN;

	getrec(userdat,U_LEECH,2,str);
	user->leech=(uchar)ahtoul(str);
	getrec(userdat,U_CURSUB,sizeof(user->cursub)-1,user->cursub);
	getrec(userdat,U_CURDIR,sizeof(user->curdir)-1,user->curdir);
	getrec(userdat,U_CURXTRN,8,user->curxtrn);

	getrec(userdat,U_FREECDT,10,str);
	user->freecdt=atol(str);

	getrec(userdat,U_XEDIT,8,str);
	for(i=0;i<cfg->total_xedits;i++)
		if(!stricmp(str,cfg->xedit[i]->code))
			break;
	user->xedit=i+1;
	if(user->xedit>cfg->total_xedits)
		user->xedit=0;

	getrec(userdat,U_SHELL,8,str);
	for(i=0;i<cfg->total_shells;i++)
		if(!stricmp(str,cfg->shell[i]->code))
			break;
	if(i==cfg->total_shells)
		i=0;
	user->shell=i;

	getrec(userdat,U_QWK,8,str);
	if(str[0]<' ') { 			   /* v1c, so set defaults */
		if(user->rest&FLAG('Q'))
rswindell's avatar
rswindell committed
			user->qwk=QWK_DEFAULT|QWK_RETCTLA;
rswindell's avatar
rswindell committed
			user->qwk=QWK_DEFAULT; 
	else
		user->qwk=ahtoul(str);

	getrec(userdat,U_TMPEXT,3,user->tmpext);
	if((!user->tmpext[0] || !strcmp(user->tmpext,"0")) && cfg->total_fcomps)
		strcpy(user->tmpext,cfg->fcomp[0]->ext);  /* For v1x to v2x conversion */

	getrec(userdat,U_CHAT,8,str);
	user->chat=ahtoul(str);
	/* Reset daily stats if not already logged on today */
	if(user->ltoday || user->etoday || user->ptoday || user->ttoday) {
		time_t		now;
		struct tm	now_tm;
		struct tm	logon_tm;

		now=time(NULL);
		if(localtime_r(&now, &now_tm)!=NULL 
			&& localtime32(&user->logontime, &logon_tm)!=NULL) {
			if(now_tm.tm_year!=logon_tm.tm_year
				|| now_tm.tm_mon!=logon_tm.tm_mon
				|| now_tm.tm_mday!=logon_tm.tm_mday)
				resetdailyuserdat(cfg,user,/* write: */FALSE);
		}
/****************************************************************************/
/* Fills the structure 'user' with info for user.number	from user.dat file	*/
/****************************************************************************/
int DLLCALL getuserdat(scfg_t* cfg, user_t *user)
{
	int		retval;
	int		file;
	char	userdat[U_LEN+1];

	if(!VALID_CFG(cfg) || user==NULL || user->number < 1)
		return(-1); 

	if((file = openuserdat(cfg, /* for_modify: */FALSE)) < 0) {
		user->number = 0;
		return file;

	memset(userdat, 0, sizeof(userdat));
	if((retval = readuserdat(cfg, user->number, userdat, file)) != 0) {
		close(file);
		return retval;
	}
	retval = parseuserdat(cfg, userdat, user);
	close(file);
	return retval;
}

/* Fast getuserdat() (leaves user.dat file open) */
int DLLCALL fgetuserdat(scfg_t* cfg, user_t *user, int file)
{
	int		retval;
	char	userdat[U_LEN+1];

	if(!VALID_CFG(cfg) || user==NULL || user->number < 1)
		return(-1); 

	memset(userdat, 0, sizeof(userdat));
	if((retval = readuserdat(cfg, user->number, userdat, file)) != 0) {
		user->number = 0;
	return parseuserdat(cfg, userdat, user);
}

/****************************************************************************/
/****************************************************************************/
static void dirtyuserdat(scfg_t* cfg, uint usernumber)
{
	int	i,file;
    node_t	node;

	for(i=1;i<=cfg->sys_nodes;i++) { /* instant user data update */
//		if(i==cfg->node_num)
//			continue;
		if(getnodedat(cfg, i,&node,NULL) != 0)
			continue;
		if(node.useron==usernumber && (node.status==NODE_INUSE
			|| node.status==NODE_QUIET)) {
			if(getnodedat(cfg, i,&node,&file) == 0) {
				node.misc|=NODE_UDAT;
				putnodedat(cfg, i,&node,file);
			}
/****************************************************************************/
/****************************************************************************/
int DLLCALL is_user_online(scfg_t* cfg, uint usernumber)
{
	int i;
	node_t	node;

	for(i=1; i<=cfg->sys_nodes; i++) {
		getnodedat(cfg, i, &node, 0);
		if((node.status==NODE_INUSE || node.status==NODE_QUIET
			|| node.status==NODE_LOGON) && node.useron==usernumber)
			return i; 
	}
	return 0;
}

/****************************************************************************/
/* Writes into user.number's slot in user.dat data in structure 'user'      */
/* Called from functions newuser, useredit and main                         */
/****************************************************************************/
int DLLCALL putuserdat(scfg_t* cfg, user_t* user)
    char	userdat[U_LEN],str[MAX_PATH+1];
	if(user==NULL)
		return(-1);

	if(!VALID_CFG(cfg) || user->number<1)
		return(-1); 

	memset(userdat,ETX,U_LEN);
	putrec(userdat,U_ALIAS,LEN_ALIAS+5,user->alias);
	putrec(userdat,U_NAME,LEN_NAME,user->name);
	putrec(userdat,U_HANDLE,LEN_HANDLE,user->handle);
	putrec(userdat,U_HANDLE+LEN_HANDLE,2,crlf);

	putrec(userdat,U_NOTE,LEN_NOTE,user->note);
	putrec(userdat,U_COMP,LEN_COMP,user->comp);
	putrec(userdat,U_COMP+LEN_COMP,2,crlf);

	putrec(userdat,U_COMMENT,LEN_COMMENT,user->comment);
	putrec(userdat,U_COMMENT+LEN_COMMENT,2,crlf);

	putrec(userdat,U_NETMAIL,LEN_NETMAIL,user->netmail);
	putrec(userdat,U_NETMAIL+LEN_NETMAIL,2,crlf);

	putrec(userdat,U_ADDRESS,LEN_ADDRESS,user->address);
	putrec(userdat,U_LOCATION,LEN_LOCATION,user->location);
	putrec(userdat,U_ZIPCODE,LEN_ZIPCODE,user->zipcode);
	putrec(userdat,U_ZIPCODE+LEN_ZIPCODE,2,crlf);

	putrec(userdat,U_PASS,LEN_PASS,user->pass);
	putrec(userdat,U_PHONE,LEN_PHONE,user->phone);
	putrec(userdat,U_BIRTH,LEN_BIRTH,user->birth);
	putrec(userdat,U_MODEM,LEN_MODEM,user->modem);
	putrec(userdat,U_IPADDR,LEN_IPADDR,user->ipaddr);
rswindell's avatar
rswindell committed
	putrec(userdat,U_LASTON,8,ultoa((ulong)user->laston,str,16));
	putrec(userdat,U_FIRSTON,8,ultoa((ulong)user->firston,str,16));
	putrec(userdat,U_EXPIRE,8,ultoa((ulong)user->expire,str,16));
	putrec(userdat,U_PWMOD,8,ultoa((ulong)user->pwmod,str,16));
	putrec(userdat,U_PWMOD+8,2,crlf);

	putrec(userdat,U_LOGONS,5,ultoa(user->logons,str,10));
	putrec(userdat,U_LTODAY,5,ultoa(user->ltoday,str,10));
	putrec(userdat,U_TIMEON,5,ultoa(user->timeon,str,10));
	putrec(userdat,U_TEXTRA,5,ultoa(user->textra,str,10));
	putrec(userdat,U_TTODAY,5,ultoa(user->ttoday,str,10));
	putrec(userdat,U_TLAST,5,ultoa(user->tlast,str,10));
	putrec(userdat,U_POSTS,5,ultoa(user->posts,str,10));
	putrec(userdat,U_EMAILS,5,ultoa(user->emails,str,10));
	putrec(userdat,U_FBACKS,5,ultoa(user->fbacks,str,10));
	putrec(userdat,U_ETODAY,5,ultoa(user->etoday,str,10));
	putrec(userdat,U_PTODAY,5,ultoa(user->ptoday,str,10));
	putrec(userdat,U_PTODAY+5,2,crlf);

	putrec(userdat,U_ULB,10,ultoa(user->ulb,str,10));
	putrec(userdat,U_ULS,5,ultoa(user->uls,str,10));
	putrec(userdat,U_DLB,10,ultoa(user->dlb,str,10));
	putrec(userdat,U_DLS,5,ultoa(user->dls,str,10));
	putrec(userdat,U_CDT,10,ultoa(user->cdt,str,10));
	putrec(userdat,U_MIN,10,ultoa(user->min,str,10));
	putrec(userdat,U_MIN+10,2,crlf);

	putrec(userdat,U_LEVEL,2,ultoa(user->level,str,10));
	putrec(userdat,U_FLAGS1,8,ultoa(user->flags1,str,16));
	putrec(userdat,U_TL,2,nulstr);	/* unused */
	putrec(userdat,U_FLAGS2,8,ultoa(user->flags2,str,16));
	putrec(userdat,U_EXEMPT,8,ultoa(user->exempt,str,16));
	putrec(userdat,U_REST,8,ultoa(user->rest,str,16));
	putrec(userdat,U_REST+8,2,crlf);

	putrec(userdat,U_ROWS,2,ultoa(user->rows,str,10));
	userdat[U_SEX]=user->sex;
	userdat[U_PROT]=user->prot;
	putrec(userdat,U_MISC,8,ultoa(user->misc,str,16));
	putrec(userdat,U_LEECH,2,ultoa(user->leech,str,16));
	putrec(userdat,U_CURSUB,sizeof(user->cursub)-1,user->cursub);
	putrec(userdat,U_CURDIR,sizeof(user->curdir)-1,user->curdir);
	putrec(userdat,U_CURXTRN,8,user->curxtrn);
	putrec(userdat,U_CURXTRN+8,2,crlf);

	putrec(userdat,U_XFER_CMD+LEN_XFER_CMD,2,crlf);

deuce's avatar
deuce committed
	putrec(userdat,U_IPADDR+LEN_IPADDR,2,crlf);

	putrec(userdat,U_FREECDT,10,ultoa(user->freecdt,str,10));

	putrec(userdat,U_FLAGS3,8,ultoa(user->flags3,str,16));
	putrec(userdat,U_FLAGS4,8,ultoa(user->flags4,str,16));

	if(user->xedit)
		putrec(userdat,U_XEDIT,8,cfg->xedit[user->xedit-1]->code);
	else
		putrec(userdat,U_XEDIT,8,nulstr);

	putrec(userdat,U_SHELL,8,cfg->shell[user->shell]->code);

	putrec(userdat,U_QWK,8,ultoa(user->qwk,str,16));
	putrec(userdat,U_TMPEXT,3,user->tmpext);
	putrec(userdat,U_CHAT,8,ultoa(user->chat,str,16));
rswindell's avatar
rswindell committed
	putrec(userdat,U_NS_TIME,8,ultoa((ulong)user->ns_time,str,16));
	putrec(userdat,U_LOGONTIME,8,ultoa((ulong)user->logontime,str,16));
	putrec(userdat,U_UNUSED,U_LEN-(U_UNUSED)-2,crlf);
	putrec(userdat,U_UNUSED+(U_LEN-(U_UNUSED)-2),2,crlf);
	if((file=openuserdat(cfg, /* for_modify: */TRUE)) < 0)
	if(filelength(file)<((long)user->number-1)*U_LEN) {
		close(file);
		return(-4);
	}

	lseek(file,(long)((long)((long)user->number-1)*U_LEN),SEEK_SET);

	i=0;
	while(i<LOOP_NODEDAB
		&& lock(file,(long)((long)(user->number-1)*U_LEN),U_LEN)==-1) {
		i++; 
	}

	if(i>=LOOP_NODEDAB) {
		close(file);
		return(-2); 
	}

	if(write(file,userdat,U_LEN)!=U_LEN) {
		unlock(file,(long)((long)(user->number-1)*U_LEN),U_LEN);
		close(file);
		return(-3); 
	}
	unlock(file,(long)((long)(user->number-1)*U_LEN),U_LEN);
	close(file);
	return(0);
}


/****************************************************************************/
/* Returns the username in 'str' that corresponds to the 'usernumber'       */
/* Called from functions everywhere                                         */
/****************************************************************************/
char* DLLCALL username(scfg_t* cfg, int usernumber, char *name)
		return(NULL);

	if(!VALID_CFG(cfg) || usernumber<1) {
	SAFEPRINTF(str,"%suser/name.dat",cfg->data_dir);
	if(flength(str)<1L) {
	if((file=nopen(str,O_RDONLY))==-1) {
	if(filelength(file)<(long)((long)usernumber*(LEN_ALIAS+2))) {
		close(file);
	lseek(file,(long)((long)(usernumber-1)*(LEN_ALIAS+2)),SEEK_SET);
	close(file);
	for(c=0;c<LEN_ALIAS;c++)
		if(name[c]==ETX) break;
	name[c]=0;
		strcpy(name,"DELETED USER");
	return(name);
/****************************************************************************/
/* Puts 'name' into slot 'number' in user/name.dat							*/
/****************************************************************************/
int DLLCALL putusername(scfg_t* cfg, int number, char *name)
	int wr;
	if(!VALID_CFG(cfg) || name==NULL || number<1) 
	SAFEPRINTF(str,"%suser/name.dat", cfg->data_dir);
	if((file=nopen(str,O_RDWR|O_CREAT))==-1) 
		return(errno); 

	/* Truncate corrupted name.dat */
	total_users=lastuser(cfg);
	if((uint)(length/(LEN_ALIAS+2))>total_users)
		chsize(file,total_users*(LEN_ALIAS+2));

	if(length && length%(LEN_ALIAS+2)) {
		close(file);
		return(-3); 
	}
	if(length<(((long)number-1)*(LEN_ALIAS+2))) {
		SAFEPRINTF2(str,"%*s",LEN_ALIAS,nulstr);
		memset(str,ETX,LEN_ALIAS);
		strcat(str,crlf);
		lseek(file,0L,SEEK_END);
		while(filelength(file)<((long)number*(LEN_ALIAS+2)))
			write(file,str,(LEN_ALIAS+2)); 
	}
	lseek(file,(long)(((long)number-1)*(LEN_ALIAS+2)),SEEK_SET);
	putrec(str,0,LEN_ALIAS,name);
	putrec(str,LEN_ALIAS,2,crlf);
	wr=write(file,str,LEN_ALIAS+2);
	if(wr!=LEN_ALIAS+2)
		return(errno);
/****************************************************************************/
/* Returns the age derived from the string 'birth' in the format MM/DD/YY	*/
/****************************************************************************/
uint DLLCALL getage(scfg_t* cfg, char *birth)
	if(!VALID_CFG(cfg) || birth==NULL)
		return(0);

	if(!atoi(birth) || !atoi(birth+3))	/* Invalid */
		return(0);

	now=time(NULL);
	age=(tm.tm_year)-(((birth[6]&0xf)*10)+(birth[7]&0xf));
	tm.tm_mon++;	/* convert to 1 based */
	if(cfg->sys_misc&SM_EURODATE) {		/* DD/MM/YY format */
		if(atoi(birth)>31 || atoi(birth+3)>12)
			return(0);
		if(((birth[3]&0xf)*10)+(birth[4]&0xf)>tm.tm_mon ||
			(((birth[3]&0xf)*10)+(birth[4]&0xf)==tm.tm_mon &&
			((birth[0]&0xf)*10)+(birth[1]&0xf)>tm.tm_mday))
			age--; 
	} else {							/* MM/DD/YY format */
		if(atoi(birth)>12 || atoi(birth+3)>31)
			return(0);
		if(((birth[0]&0xf)*10)+(birth[1]&0xf)>tm.tm_mon ||
			(((birth[0]&0xf)*10)+(birth[1]&0xf)==tm.tm_mon &&
			((birth[3]&0xf)*10)+(birth[4]&0xf)>tm.tm_mday))
	return(age);
}

/****************************************************************************/
/* Reads the data for node number 'number' into the structure 'node'        */
/****************************************************************************/
int DLLCALL getnodedat(scfg_t* cfg, uint number, node_t *node, int* fdp)
	int		count=0;
	if(!VALID_CFG(cfg) 
		|| node==NULL || number<1 || number>cfg->sys_nodes)
	memset(node,0,sizeof(node_t));
	SAFEPRINTF(str,"%snode.dab",cfg->ctrl_dir);
	if((file=nopen(str,O_RDWR|O_DENYNONE))==-1)
	if(filelength(file)>=(long)(number*sizeof(node_t))) {
		number--;	/* make zero based */
		for(count=0;count<LOOP_NODEDAB;count++) {
			if(count)
				mswait(100);
			lseek(file,(long)number*sizeof(node_t),SEEK_SET);
			if(fdp!=NULL 
				&& lock(file,(long)number*sizeof(node_t),sizeof(node_t))!=0) 
			rd=read(file,node,sizeof(node_t));
				unlock(file,(long)number*sizeof(node_t),sizeof(node_t));
			if(rd==sizeof(node_t))
	if(fdp==NULL || count==LOOP_NODEDAB)
	if(count==LOOP_NODEDAB) 
		return(-2); 
	
	return(0);
}

/****************************************************************************/
/* Write the data from the structure 'node' into node.dab  					*/
/****************************************************************************/
int DLLCALL putnodedat(scfg_t* cfg, uint number, node_t* node, int file)
	if(!VALID_CFG(cfg) 
		|| node==NULL || number<1 || number>cfg->sys_nodes) {

	number--;	/* make zero based */
	for(attempts=0;attempts<10;attempts++) {
		lseek(file,(long)number*sizeof(node_t),SEEK_SET);
		if((wr=write(file,node,sizeof(node_t)))==sizeof(node_t))
			break;
		wrerr=errno;	/* save write error */
		mswait(100);
	}
	unlock(file,(long)number*sizeof(node_t),sizeof(node_t));
	close(file);

	if(wr!=sizeof(node_t))
		return(wrerr);
/****************************************************************************/
/* Packs the password 'pass' into 5bit ASCII inside node_t. 32bits in 		*/
/* node.extaux, and the other 8bits in the upper byte of node.aux			*/
/****************************************************************************/
void DLLCALL packchatpass(char *pass, node_t *node)
{
	char	bits;
	int		i,j;

	if(pass==NULL || node==NULL)
		return;

	node->aux&=~0xff00;		/* clear the password */
	node->extaux=0L;
	if((j=strlen(pass))==0) /* there isn't a password */
		return;
	node->aux|=(int)((pass[0]-64)<<8);  /* 1st char goes in low 5bits of aux */
	if(j==1)	/* password is only one char, we're done */
		return;
	node->aux|=(int)((pass[1]-64)<<13); /* low 3bits of 2nd char go in aux */
	node->extaux|=(long)((pass[1]-64)>>3); /* high 2bits of 2nd char go extaux */
	bits=2;
	for(i=2;i<j;i++) {	/* now process the 3rd char through the last */
		node->extaux|=(long)((long)(pass[i]-64)<<bits);
		bits+=5; 
	}
}

/****************************************************************************/
/* Unpacks the password 'pass' from the 5bit ASCII inside node_t. 32bits in */
/* node.extaux, and the other 8bits in the upper byte of node.aux			*/
/****************************************************************************/
char* DLLCALL unpackchatpass(char *pass, node_t* node)
{
	char 	bits;
	int 	i;

	if(pass==NULL || node==NULL)
		return(NULL);

	pass[0]=(node->aux&0x1f00)>>8;
	pass[1]=(char)(((node->aux&0xe000)>>13)|((node->extaux&0x3)<<3));
	bits=2;
	for(i=2;i<8;i++) {
		pass[i]=(char)((node->extaux>>bits)&0x1f);
		bits+=5; 
	}
	pass[8]=0;
	for(i=0;i<8;i++)
		if(pass[i])
			pass[i]+=64;
	return(pass);
}

static char* node_connection_desc(ushort conn, char* str)
{
	switch(conn) {
		case NODE_CONNECTION_LOCAL:
			strcpy(str,"Locally");
			break;
		case NODE_CONNECTION_TELNET:
			strcpy(str,"via telnet");
			break;
		case NODE_CONNECTION_RLOGIN:
			strcpy(str,"via rlogin");
			break;
		case NODE_CONNECTION_SSH:
			strcpy(str,"via ssh");
			break;
		default:
			sprintf(str,"at %ubps",conn);
			break;
	}

	return str;
}

char* DLLCALL nodestatus(scfg_t* cfg, node_t* node, char* buf, size_t buflen)
	if(node==NULL) {
		strncpy(buf,"(null)",buflen);
		return(buf);
            SAFECOPY(str,"Waiting for connection");
            break;
        case NODE_OFFLINE:
            strcpy(str,"Offline");
            break;
        case NODE_NETTING:	/* Obsolete */
				,node_connection_desc(node->connection, tmp));
		case NODE_LOGOUT:
			SAFEPRINTF(str,"Logging out %s", username(cfg,node->useron,tmp));
			break;
            SAFECOPY(str,"Waiting for all nodes to become inactive");
            SAFEPRINTF(str,"Waiting for node %d to finish external event"
                ,node->aux);
            break;
        case NODE_EVENT_RUNNING:
            SAFECOPY(str,"Running external event");
            SAFEPRINTF(str,"New user applying for access %s"
				,node_connection_desc(node->connection, tmp));
            break;
        case NODE_QUIET:
        case NODE_INUSE:
            username(cfg,node->useron,str);
            strcat(str," ");
            switch(node->action) {
                case NODE_MAIN:
                    strcat(str,"at main menu");
                    break;
                case NODE_RMSG:
                    strcat(str,"reading messages");
                    break;
                case NODE_RMAL:
                    strcat(str,"reading mail");
                    break;
                case NODE_RSML:
                    strcat(str,"reading sent mail");
                    break;
                case NODE_RTXT:
                    strcat(str,"reading text files");
                    break;
                case NODE_PMSG:
                    strcat(str,"posting message");
                    break;
                case NODE_SMAL:
                    strcat(str,"sending mail");
                    break;
                case NODE_AMSG: