Skip to content
Snippets Groups Projects
userdat.c 124 KiB
Newer Older
/****************************************************************************/
/****************************************************************************/
bool logoutuserdat(scfg_t* cfg, user_t* user, time_t now, time_t logontime)
	time_t tused;
	if(user==NULL)
	tused=(now-logontime)/60;
	user->tlast=(ushort)(tused > USHRT_MAX ? USHRT_MAX : tused);
	putuserdatetime(cfg,user->number, USER_LASTON, (time32_t)now);
	putuserstr(cfg,user->number, USER_TLAST, ultoa(user->tlast,str,10));
	adjustuserval(cfg,user->number, USER_TIMEON, user->tlast);
	adjustuserval(cfg,user->number, USER_TTODAY, user->tlast);
	if(localtime_r(&logontime,&tm)==NULL)
	if(tm.tm_mday!=tm_now.tm_mday)
		resetdailyuserdat(cfg, user, /* write: */true);

/****************************************************************************/
/****************************************************************************/
void resetdailyuserdat(scfg_t* cfg, user_t* user, bool write)
	if(!VALID_CFG(cfg) || user==NULL)
		return;

	user->ltoday=0;
	if(write) putuserstr(cfg,user->number, USER_LTODAY, "0");
	user->etoday=0;
	if(write) putuserstr(cfg,user->number, USER_ETODAY, "0");
	user->ptoday=0;
	if(write) putuserstr(cfg,user->number, USER_PTODAY, "0");
	/* free credits per day */
	user->freecdt=cfg->level_freecdtperday[user->level];
	if(write) putuserdec64(cfg,user->number, USER_FREECDT, user->freecdt);
	if(write) putuserstr(cfg,user->number, USER_TTODAY, "0");
	if(write) putuserstr(cfg,user->number, USER_TEXTRA, "0");

/****************************************************************************/
/* Get dotted-equivalent email address for user 'name'.						*/
rswindell's avatar
rswindell committed
/* 'addr' is the target buffer for the full address.						*/
/* Pass cfg=NULL to NOT have "@address" portion appended.					*/
/****************************************************************************/
char* usermailaddr(scfg_t* cfg, char* addr, const char* name)
rswindell's avatar
rswindell committed
	if(addr==NULL || name==NULL)
	if(strchr(name,'@')!=NULL) { /* Avoid double-@ */
		strcpy(addr,name);
		return(addr);
	}
	if(strchr(name,'.') && strchr(name,' ')) {
		/* convert "Dr. Seuss" to "Dr.Seuss" */
		strip_space(name,addr);
	} else if(strchr(name,'!')) {
		sprintf(addr,"\"%s\"",name);
	} else {
		strcpy(addr,name);
		/* convert "first last" to "first.last" */
		for(i=0;addr[i];i++)
			if(addr[i]==' ' || addr[i]&0x80)
				addr[i]='.';
	}
rswindell's avatar
rswindell committed
	if(VALID_CFG(cfg)) {
		strcat(addr,"@");
		strcat(addr,cfg->sys_inetaddr);
	}
/****************************************************************************/
/* Convert a sender's name/address from a message header into Synchronet	*/
/* SMTP-server-routable form												*/
/****************************************************************************/
void smtp_netmailaddr(scfg_t* cfg, smbmsg_t* msg, char* name, size_t namelen, char* addr, size_t addrlen)
{
	char addrbuf[256];

	if(name != NULL)
		snprintf(name, namelen, "\"%s\"", msg->from);
	if(msg->from_net.type==NET_QWK && msg->from_net.addr!=NULL)
		snprintf(addr, addrlen, "%s!%s"
			,(char*)msg->from_net.addr
			,usermailaddr(cfg, addrbuf, msg->from));
	else if(msg->from_net.type==NET_FIDO && msg->from_net.addr!=NULL) {
		faddr_t* faddr = (faddr_t *)msg->from_net.addr;
		char faddrstr[128];
		if(name != NULL)
			snprintf(name, namelen, "\"%s\" (%s)", msg->from, smb_faddrtoa(faddr, faddrstr));
		if(faddr->point)
			SAFEPRINTF4(faddrstr,"p%hu.f%hu.n%hu.z%hu"FIDO_TLD
				,faddr->point, faddr->node, faddr->net, faddr->zone);
		else
			SAFEPRINTF3(faddrstr,"f%hu.n%hu.z%hu"FIDO_TLD
				,faddr->node, faddr->net, faddr->zone);
		snprintf(addr, addrlen, "%s@%s", usermailaddr(NULL, addrbuf, msg->from), faddrstr);
	} else if(msg->from_net.type!=NET_NONE && msg->from_net.addr!=NULL)
		snprintf(addr, addrlen, "%s", (char*)msg->from_net.addr);
char* alias(scfg_t* cfg, const char* name, char* buf)
	size_t	namelen;
	size_t	cmplen;
	if(!VALID_CFG(cfg) || name==NULL || buf==NULL)
		return(NULL);

	p=(char*)name;
	SAFEPRINTF(fname,"%salias.cfg",cfg->ctrl_dir);
	if((fp = fnopen(NULL, fname, O_RDONLY)) == NULL)
		return((char*)name);
		if(!fgets(line,sizeof(line),fp))
		if(*np==';' || *np==0)	/* no name value, or comment */

		if(*tp==0)	/* no alias value */
			continue;
		*tp=0;

		vp=tp+1;
		SKIP_WHITESPACE(vp);
		truncsp(vp);
		if(*vp==0)	/* no value */
			continue;

		if(*np=='*') {
			np++;
			cmplen=strlen(np);
			namelen=strlen(name);
			if(namelen<cmplen)
				continue;
			if(strnicmp(np,name+(namelen-cmplen),cmplen))
				continue;
				sprintf(buf,"%.*s%s",(int)(namelen-cmplen),name,vp+1);
int newuserdefaults(scfg_t* cfg, user_t* user)
{
	int i;

	user->sex = ' ';

	/* statistics */
	user->firston=user->laston=user->pwmod=time32(NULL);

	/* security */
	user->level=cfg->new_level;
	user->flags1=cfg->new_flags1;
	user->flags2=cfg->new_flags2;
	user->flags3=cfg->new_flags3;
	user->flags4=cfg->new_flags4;
	user->rest=cfg->new_rest;
	user->exempt=cfg->new_exempt;

	user->cdt=cfg->new_cdt;
	user->min=cfg->new_min;
	user->freecdt=cfg->level_freecdtperday[user->level];
	if(cfg->new_expire)
		user->expire=user->firston+((long)cfg->new_expire*24L*60L*60L);
	else
		user->expire=0;

	/* settings */
	if(cfg->total_fcomps)
		SAFECOPY(user->tmpext,cfg->fcomp[0]->ext);
	else
		SAFECOPY(user->tmpext,"zip");

	user->shell = cfg->new_shell;
	user->misc = cfg->new_misc|(AUTOTERM|COLOR);
	user->misc &= ~(DELETED|INACTIVE|QUIET|NETMAIL);
	user->prot = cfg->new_prot;
	user->qwk = cfg->new_qwk;
	user->chat = cfg->new_chat;

	for(i=0;i<cfg->total_xedits;i++)
		if(!stricmp(cfg->xedit[i]->code,cfg->new_xedit) && chk_ar(cfg,cfg->xedit[i]->ar, user, /* client: */NULL))
			break;
	if(i<cfg->total_xedits)
		user->xedit=i+1;

	return 0;
}

int newuserdat(scfg_t* cfg, user_t* user)
{
	char	str[MAX_PATH+1];
	char	tmp[128];
	int		c;
	int		err;
	int		file;
	int		unum=1;
	int		last;
	FILE*	stream;
	if(!VALID_CFG(cfg) || user==NULL)
		return USER_INVALID_ARG;
	SAFEPRINTF(str,"%suser/name.dat",cfg->data_dir);
	if(fexist(str)) {
		if((stream=fnopen(&file,str,O_RDONLY))==NULL) {
			return USER_OPEN_ERROR;
		last=(long)filelength(file)/(LEN_ALIAS+2);	   /* total users */
		while(unum<=last) {
			if(fread(str,LEN_ALIAS+2,1,stream) != 1)
				memset(str, ETX, LEN_ALIAS);
			for(c=0;c<LEN_ALIAS;c++)
				if(str[c]==ETX) break;
			str[c]=0;
			if(!c) {	 /* should be a deleted user */
				user_t deluser;
				deluser.number = unum;
				if(getuserdat(cfg, &deluser) == 0) {
					if(deluser.misc&DELETED) {	 /* deleted bit set too */
						if((time(NULL)-deluser.laston)/86400>=cfg->sys_deldays)
							break; /* deleted long enough ? */
					}
		fclose(stream);
	}

	last=lastuser(cfg);		/* Check against data file */

	if(unum>last+1) 		/* Corrupted name.dat? */
		unum=last+1;
	else if(unum<=last) {	/* Overwriting existing user */
		user_t deluser;
		deluser.number = unum;
		if(getuserdat(cfg, &deluser) == 0) {
			if(!(deluser.misc&DELETED)) /* Not deleted? Set usernumber to end+1 */
				unum=last+1;
		}
	}

	user->number=unum;		/* store the new user number */

	if((err=putusername(cfg,user->number,user->alias)) != USER_SUCCESS)
		return(err);

	if((err=putuserdat(cfg,user)) != USER_SUCCESS)
		return(err);

	SAFEPRINTF2(str,"%sfile/%04u.in",cfg->data_dir,user->number);  /* delete any files */
	delfiles(str, ALLFILES, /* keep: */0);                         /* waiting for user */
	rmdir(str);
	SAFEPRINTF2(str,"%sfile/%04u.out",cfg->data_dir,user->number); /* delete any files */
	delfiles(str, ALLFILES, /* keep: */0);                         /* pending transmit to/by user */
	rmdir(str);
	SAFEPRINTF(tmp,"%04u.*",user->number);
	SAFEPRINTF(str,"%sfile",cfg->data_dir);
	SAFEPRINTF(str,"%suser",cfg->data_dir);
	SAFEPRINTF(str,"%smsgs",cfg->data_dir);
	delfiles(str,tmp, /* keep: */0);
	SAFEPRINTF2(str,"%suser/%04u",cfg->data_dir,user->number);
Rob Swindell's avatar
Rob Swindell committed
	SAFEPRINTF2(str,"%suser/ptrs/%04u.ixb",cfg->data_dir,user->number); /* legacy msg ptrs */
	remove(str);

	/* Update daily statistics database (for system and node) */

	for(i=0;i<2;i++) {
		FILE* fp = fopen_dstats(cfg, i ? cfg->node_num : 0, /* for_write: */true);
Rob Swindell's avatar
Rob Swindell committed
		if(fp == NULL)
Rob Swindell's avatar
Rob Swindell committed
		if(fread_dstats(fp, &stats)) {
			stats.today.nusers++;
			stats.total.nusers++;
			fwrite_dstats(fp, &stats, __FUNCTION__);
Rob Swindell's avatar
Rob Swindell committed
		}
		fclose_dstats(fp);
	return USER_SUCCESS;
size_t user_field_len(enum user_field fnum)
{
	user_t user;

	switch(fnum) {
		case USER_ALIAS:	return sizeof(user.alias) - 1;
		case USER_NAME:		return sizeof(user.name) - 1;
		case USER_HANDLE:	return sizeof(user.handle) -1;
		case USER_NOTE:		return sizeof(user.note) - 1;
		case USER_IPADDR:	return sizeof(user.ipaddr) - 1;
		case USER_HOST:		return sizeof(user.comp) - 1;
		case USER_NETMAIL:	return sizeof(user.netmail) - 1;
		case USER_ADDRESS:	return sizeof(user.address) - 1;
		case USER_LOCATION:	return sizeof(user.location) - 1;
		case USER_ZIPCODE:	return sizeof(user.zipcode) - 1;
		case USER_PHONE:	return sizeof(user.phone) - 1;
		case USER_BIRTH:	return sizeof(user.birth) - 1;
		case USER_GENDER:	return sizeof(user.sex);
		case USER_COMMENT:	return sizeof(user.comment) - 1;
		case USER_CONNECTION: return sizeof(user.modem) -1;

		// Bit-fields:
		case USER_MISC:		return sizeof(user.misc);
		case USER_QWK:		return sizeof(user.qwk);
		case USER_CHAT:		return sizeof(user.chat);

		// Settings:
		case USER_ROWS:		return sizeof(user.rows);
		case USER_COLS:		return sizeof(user.cols);
		case USER_XEDIT:	return sizeof(user.xedit);
		case USER_SHELL:	return sizeof(user.shell);
		case USER_TMPEXT:	return sizeof(user.tmpext) - 1;
		case USER_PROT:		return sizeof(user.prot);
		case USER_CURSUB:	return sizeof(user.cursub) - 1;
		case USER_CURDIR:	return sizeof(user.curdir) - 1;
		case USER_CURXTRN:	return sizeof(user.curxtrn) - 1;

		// Date/times:
		case USER_LOGONTIME:	return sizeof(user.logontime);
		case USER_NS_TIME:		return sizeof(user.ns_time);
		case USER_LASTON:		return sizeof(user.laston);
		case USER_FIRSTON:		return sizeof(user.firston);

		// Counting stats:
		case USER_LOGONS:		return sizeof(user.logons);
		case USER_LTODAY:		return sizeof(user.ltoday);
		case USER_TIMEON:		return sizeof(user.timeon);
		case USER_TTODAY:		return sizeof(user.ttoday);
		case USER_TLAST:		return sizeof(user.tlast);
		case USER_POSTS:		return sizeof(user.posts);
		case USER_EMAILS:		return sizeof(user.emails);
		case USER_FBACKS:		return sizeof(user.fbacks);
		case USER_ETODAY:		return sizeof(user.etoday);
		case USER_PTODAY:		return sizeof(user.ptoday);

		// File xfer stats:
		case USER_ULB:			return sizeof(user.ulb);
		case USER_ULS:			return sizeof(user.uls);
		case USER_DLB:			return sizeof(user.dlb);
		case USER_DLS:			return sizeof(user.dls);
		case USER_DLCPS:		return sizeof(user.dlcps);
		case USER_LEECH:		return sizeof(user.leech);

		// Security:
		case USER_PASS:			return sizeof(user.pass) - 1;
		case USER_PWMOD:		return sizeof(user.pwmod);
		case USER_LEVEL:		return sizeof(user.level);
		case USER_FLAGS1:		return sizeof(user.flags1);
		case USER_FLAGS2:		return sizeof(user.flags2);
		case USER_FLAGS3:		return sizeof(user.flags3);
		case USER_FLAGS4:		return sizeof(user.flags4);
		case USER_EXEMPT:		return sizeof(user.exempt);
		case USER_REST:			return sizeof(user.rest);
		case USER_CDT:			return sizeof(user.cdt);
		case USER_FREECDT:		return sizeof(user.freecdt);
		case USER_MIN:			return sizeof(user.min);
		case USER_TEXTRA:		return sizeof(user.textra);
		case USER_EXPIRE:		return sizeof(user.expire);

		default:			return 0;
	}
}

/****************************************************************************/
/* Determine if the specified user can or cannot access the specified sub	*/
/****************************************************************************/
bool can_user_access_sub(scfg_t* cfg, int subnum, user_t* user, client_t* client)
rswindell's avatar
rswindell committed
	if(!chk_ar(cfg,cfg->grp[cfg->sub[subnum]->grp]->ar,user,client))
rswindell's avatar
rswindell committed
	if(!chk_ar(cfg,cfg->sub[subnum]->ar,user,client))
}

/****************************************************************************/
/* Determine if the specified user can or cannot read the specified sub		*/
/****************************************************************************/
bool can_user_read_sub(scfg_t* cfg, int subnum, user_t* user, client_t* client)
{
	if(!can_user_access_sub(cfg, subnum, user, client))
	return chk_ar(cfg,cfg->sub[subnum]->read_ar,user,client);
}

/****************************************************************************/
/* Determine if the specified user can or cannot post on the specified sub	*/
/* 'reason' is an (optional) pointer to a text.dat item number, indicating	*/
/* the reason the user cannot post, when returning false.					*/
/****************************************************************************/
bool can_user_post(scfg_t* cfg, int subnum, user_t* user, client_t* client, uint* reason)
	if(!can_user_access_sub(cfg, subnum, user, client))
rswindell's avatar
rswindell committed
	if(!chk_ar(cfg,cfg->sub[subnum]->post_ar,user,client))
	if(cfg->sub[subnum]->misc&(SUB_QNET|SUB_FIDO|SUB_PNET|SUB_INET)
		&& user->rest&FLAG('N'))		/* network restriction? */
	if((cfg->sub[subnum]->misc & SUB_NAME)
		&& (user->rest & (FLAG('Q') | FLAG('O'))) == FLAG('O'))
	if(reason!=NULL)
		*reason=R_Post;
	if(user->rest&FLAG('P'))			/* post restriction? */
	if(reason!=NULL)
		*reason=TooManyPostsToday;
	if(user->ptoday>=cfg->level_postsperday[user->level])
/****************************************************************************/
// Determine if the specified user can access one or more directories of lib
/****************************************************************************/
bool can_user_access_lib(scfg_t* cfg, int libnum, user_t* user, client_t* client)
	for(int dirnum = 0; dirnum < cfg->total_dirs; dirnum++) {
		if(cfg->dir[dirnum]->lib != libnum)
			continue;
		if(can_user_access_dir(cfg, dirnum, user, client)) // checks lib's AR already
			count++;
	}
	return count >= 1; // User has access to one or more directories of library
}

/****************************************************************************/
// Determine if the specified user can access ALL file libraries
/****************************************************************************/
bool can_user_access_all_libs(scfg_t* cfg, user_t* user, client_t* client)
	for(int libnum = 0; libnum < cfg->total_libs; libnum++) {
		if(!can_user_access_lib(cfg, libnum, user, client))
}

/****************************************************************************/
// Determine if the specified user can all dirs of a lib
/****************************************************************************/
bool can_user_access_all_dirs(scfg_t* cfg, int libnum, user_t* user, client_t* client)
	for(int dirnum = 0; dirnum < cfg->total_dirs; dirnum++) {
		if(cfg->dir[dirnum]->lib != libnum)
			continue;
		if(can_user_access_dir(cfg, dirnum, user, client)) // checks lib's AR already
			count++;
		else
	}
	return count >= 1; // User has access to one or more directories of library
}

/****************************************************************************/
/* Determine if the specified user can or cannot access the specified dir	*/
/****************************************************************************/
bool can_user_access_dir(scfg_t* cfg, int dirnum, user_t* user, client_t* client)
	if(!chk_ar(cfg,cfg->lib[cfg->dir[dirnum]->lib]->ar,user,client))
	if(!chk_ar(cfg,cfg->dir[dirnum]->ar,user,client))
}

/****************************************************************************/
/* Determine if the specified user can or cannot upload files to the dirnum	*/
/* 'reason' is an (optional) pointer to a text.dat item number, indicating	*/
/* the reason the user cannot post, when returning false.					*/
/****************************************************************************/
bool can_user_upload(scfg_t* cfg, int dirnum, user_t* user, client_t* client, uint* reason)
	if(!can_user_access_dir(cfg, dirnum, user, client))
	if(reason!=NULL)
		*reason=R_Upload;
	if(user->rest&FLAG('U'))			/* upload restriction? */
	if(user->rest&FLAG('T'))			/* transfer restriction? */
	if(!(user->exempt&FLAG('U'))		/* upload exemption */
		&& !is_user_dirop(cfg, dirnum, user, client)) {
		if(reason!=NULL)
			*reason=CantUploadHere;
		if(!chk_ar(cfg, cfg->lib[cfg->dir[dirnum]->lib]->ul_ar, user, client))
		if(!chk_ar(cfg, cfg->dir[dirnum]->ul_ar, user, client))
}

/****************************************************************************/
/* Determine if the specified user can or cannot download files from dirnum	*/
/* 'reason' is an (optional) pointer to a text.dat item number, indicating	*/
/* the reason the user cannot post, when returning false.					*/
/****************************************************************************/
bool can_user_download(scfg_t* cfg, int dirnum, user_t* user, client_t* client, uint* reason)
	if(dirnum != cfg->user_dir && !can_user_access_dir(cfg, dirnum, user, client))
	if(!chk_ar(cfg,cfg->lib[cfg->dir[dirnum]->lib]->dl_ar,user,client))
	if(!chk_ar(cfg,cfg->dir[dirnum]->dl_ar,user,client))
	if(reason!=NULL)
		*reason=R_Download;
	if(user->rest&FLAG('D'))			/* download restriction? */
	if(user->rest&FLAG('T'))			/* transfer restriction? */
/****************************************************************************/
/* Determine if the specified user can or cannot send email					*/
/* 'reason' is an (optional) pointer to a text.dat item number				*/
/* usernumber==0 for netmail												*/
/****************************************************************************/
bool can_user_send_mail(scfg_t* cfg, enum smb_net_type net_type, uint usernumber, user_t* user, uint* reason)
{
	if(reason!=NULL)
		*reason=R_Email;
	if(user==NULL || user->number==0)
rswindell's avatar
rswindell committed
	if(net_type==NET_NONE && usernumber>1 && user->rest&FLAG('E'))			/* local mail restriction? */
	if(reason!=NULL)
		*reason=NoNetMailAllowed;
rswindell's avatar
rswindell committed
	if(net_type!=NET_NONE && user->rest&FLAG('M'))							/* netmail restriction */
rswindell's avatar
rswindell committed
	if(net_type==NET_FIDO && !(cfg->netmail_misc&NMAIL_ALLOW))				/* Fido netmail globally disallowed */
rswindell's avatar
rswindell committed
	if(net_type==NET_INTERNET && !(cfg->inetmail_misc&NMAIL_ALLOW))			/* Internet mail globally disallowed */
rswindell's avatar
rswindell committed
	if(net_type==NET_NONE && usernumber==1 && user->rest&FLAG('S'))			/* feedback restriction? */
	if(reason!=NULL)
		*reason=TooManyEmailsToday;
	if(user->etoday>=cfg->level_emailperday[user->level] && !(user->exempt&FLAG('M')))
/****************************************************************************/
/* Determine if the specified user is a system operator						*/
/****************************************************************************/
bool is_user_sysop(user_t* user)
	return user->level >= SYSOP_LEVEL;
}

/****************************************************************************/
/* Determine if the specified user is a sub-board operator					*/
/****************************************************************************/
bool is_user_subop(scfg_t* cfg, int subnum, user_t* user, client_t* client)
	if(!can_user_access_sub(cfg, subnum, user, client))
	if(is_user_sysop(user))
	return cfg->sub[subnum]->op_ar[0]!=0 && chk_ar(cfg,cfg->sub[subnum]->op_ar,user,client);
/****************************************************************************/
/* Determine if the specified user is a directory operator					*/
/****************************************************************************/
bool is_user_dirop(scfg_t* cfg, int dirnum, user_t* user, client_t* client)
	if(!can_user_access_dir(cfg, dirnum, user, client))
	if(is_user_sysop(user))
	return (cfg->dir[dirnum]->op_ar[0]!=0 && chk_ar(cfg,cfg->dir[dirnum]->op_ar,user,client))
			|| (cfg->lib[cfg->dir[dirnum]->lib]->op_ar[0]!=0 && chk_ar(cfg,cfg->lib[cfg->dir[dirnum]->lib]->op_ar,user,client));
/****************************************************************************/
/* Determine if downloads from the specified directory are free for the		*/
/* specified user															*/
/****************************************************************************/
bool is_download_free(scfg_t* cfg, int dirnum, user_t* user, client_t* client)
	if(cfg->lib[cfg->dir[dirnum]->lib]->ex_ar[0] != 0
		&& chk_ar(cfg,cfg->lib[cfg->dir[dirnum]->lib]->ex_ar,user,client))
	if(cfg->dir[dirnum]->ex_ar[0]==0)
rswindell's avatar
rswindell committed
	return(chk_ar(cfg,cfg->dir[dirnum]->ex_ar,user,client));
/****************************************************************************/
/* Note: This function does not account for timed events!					*/
/****************************************************************************/
time_t gettimeleft(scfg_t* cfg, user_t* user, time_t starttime)
	if(user->exempt&FLAG('T')) {	/* Time online exemption */
		timeleft=cfg->level_timepercall[user->level];
		if(timeleft<10)             /* never get below 10 minutes for exempt users */
		timeleft*=60;				/* convert to seconds */
	}
	else {
		tleft=(((long)cfg->level_timeperday[user->level]-user->ttoday)
			+user->textra)*60L;
		if(tleft<0) tleft=0;
		if(tleft>cfg->level_timepercall[user->level]*60)
			tleft=cfg->level_timepercall[user->level]*60;
		tleft+=user->min*60L;
		long tused = (long)MAX(now - starttime, 0);
		tleft -= tused;
		if(tleft < 0)
			tleft = 0;
			timeleft=tleft;

/*************************************************************************/
/* Check a supplied name/alias and see if it's valid by our standards.   */
/*************************************************************************/
bool check_name(scfg_t* cfg, const char* name)
	if(name == NULL)
	if (   name[0] <= ' '			/* begins with white-space? */
		|| name[len-1] <= ' '		/* ends with white-space */
		|| !stricmp(name,cfg->sys_id)
		|| strchr(name,0xff)
		|| matchuser(cfg,name,true /* sysop_alias */)
		|| trashcan(cfg,name,"name")
		|| alias(cfg,name,tmp)!=name
 	   )
 		return false;
 	return true;
/*************************************************************************/
/* Check a supplied real name and see if it's valid by our standards.   */
/*************************************************************************/
bool check_realname(scfg_t* cfg, const char* name)

	return (uchar)name[0]<0x7f && name[1] && IS_ALPHA(name[0]) && strchr(name,' ');
}

/****************************************************************************/
/* Login attempt/hack tracking												*/
/****************************************************************************/

/****************************************************************************/
link_list_t* loginAttemptListInit(link_list_t* list)
{
	return listInit(list, LINK_LIST_MUTEX);
}

/****************************************************************************/
bool loginAttemptListFree(link_list_t* list)
/****************************************************************************/
/* Returns negative value on failure										*/
/****************************************************************************/
long loginAttemptListCount(link_list_t* list)
	long count;
	if(!listLock(list))
		return -1;
	count = listCountNodes(list);
	listUnlock(list);
	return count;
}

/****************************************************************************/
/* Returns number of items (attempts) removed from the list					*/
/* Returns negative value on failure										*/
/****************************************************************************/
long loginAttemptListClear(link_list_t* list)
	if(!listLock(list))
		return -1;
	count=listCountNodes(list);
	count-=listFreeNodes(list);
	listUnlock(list);
	return count;
}

/****************************************************************************/
deuce's avatar
deuce committed
static list_node_t* login_attempted(link_list_t* list, const union xp_sockaddr* addr)
	for(node=list->first; node!=NULL; node=node->next) {
		if(attempt->addr.addr.sa_family != addr->addr.sa_family)
			continue;
deuce's avatar
deuce committed
		switch(addr->addr.sa_family) {
			case AF_INET:
				if(memcmp(&attempt->addr.in.sin_addr, &addr->in.sin_addr, sizeof(addr->in.sin_addr)) == 0)
					return node;
deuce's avatar
deuce committed
				break;
			case AF_INET6:
				if(memcmp(&attempt->addr.in6.sin6_addr, &addr->in6.sin6_addr, sizeof(addr->in6.sin6_addr)) == 0)
					return node;
deuce's avatar
deuce committed
				break;
		}
/****************************************************************************/
/* Returns negative value on failure										*/
/****************************************************************************/
long loginAttempts(link_list_t* list, const union xp_sockaddr* addr)
	list_node_t*		node;

deuce's avatar
deuce committed
	if(addr->addr.sa_family != AF_INET && addr->addr.sa_family != AF_INET6)
		return 0;
	if(!listLock(list))
		return -1;
	if((node=login_attempted(list, addr))!=NULL)
		count = ((login_attempt_t*)node->data)->count - ((login_attempt_t*)node->data)->dupes;
	listUnlock(list);

	return count;
/****************************************************************************/
void loginSuccess(link_list_t* list, const union xp_sockaddr* addr)
deuce's avatar
deuce committed
	if(addr->addr.sa_family != AF_INET && addr->addr.sa_family != AF_INET6)
		return;
	listLock(list);
	if((node=login_attempted(list, addr)) != NULL)
		listRemoveNode(list, node, /* freeData: */true);
/****************************************************************************/
/* Returns number of *unique* login attempts (excludes consecutive dupes)	*/
/****************************************************************************/
ulong loginFailure(link_list_t* list, const union xp_sockaddr* addr, const char* prot, const char* user, const char* pass, login_attempt_t* details)
	login_attempt_t		first;
	login_attempt_t*	attempt=&first;
	ulong				count=0;
deuce's avatar
deuce committed
	if(addr->addr.sa_family != AF_INET && addr->addr.sa_family != AF_INET6)
		return 0;
	memset(&first, 0, sizeof(first));
	if(!listLock(list))
		return 0;
	if((node=login_attempted(list, addr)) != NULL) {
		attempt=node->data;
		/* Don't count consecutive duplicate attempts (same name and password): */
		if((user!=NULL && strcmp(attempt->user,user)==0) && (pass!=NULL && strcmp(attempt->pass,pass)==0))
			attempt->dupes++;
	memcpy(&attempt->addr, addr, sizeof(*addr));
	if(user != NULL)
		SAFECOPY(attempt->user, user);
	memset(attempt->pass, 0, sizeof(attempt->pass));
	if(pass != NULL)
		SAFECOPY(attempt->pass, pass);
rswindell's avatar
rswindell committed
	count = attempt->count - attempt->dupes;
	if(node==NULL) {
		attempt->first = attempt->time;
		listPushNodeData(list, attempt, sizeof(login_attempt_t));
#if !defined(NO_SOCKET_SUPPORT)
ulong loginBanned(scfg_t* cfg, link_list_t* list, SOCKET sock, const char* host_name
	,struct login_attempt_settings settings, login_attempt_t* details)
rswindell's avatar
rswindell committed
	list_node_t*		node;
	login_attempt_t*	attempt;
	bool				result = false;
rswindell's avatar
rswindell committed
	time32_t			now = time32(NULL);
	union xp_sockaddr	client_addr;
	union xp_sockaddr	server_addr;
	socklen_t			addr_len;
	char				exempt[MAX_PATH+1];

	SAFEPRINTF2(exempt, "%s%s", cfg->ctrl_dir, strIpFilterExemptConfigFile);

	if(list==NULL)
		return 0;

	addr_len=sizeof(server_addr);
	if((result=getsockname(sock, &server_addr.addr, &addr_len)) != 0)
		return 0;

	addr_len=sizeof(client_addr);
	if((result=getpeername(sock, &client_addr.addr, &addr_len)) != 0)
		return 0;

	/* Don't ban connections from the server back to itself */
	if(inet_addrmatch(&server_addr, &client_addr))
		return 0;
	if(inet_addrtop(&client_addr, ip_addr, sizeof(ip_addr)) != NULL
		&& find2strs(ip_addr, host_name, exempt, NULL))
	if(!listLock(list))
		return 0;
	node = login_attempted(list, &client_addr);
rswindell's avatar
rswindell committed
	listUnlock(list);
	if(node == NULL)
		return 0;
	attempt = node->data;
	SAFECOPY(name, attempt->user);
	truncstr(name, "@");
rswindell's avatar
rswindell committed
	if(((settings.tempban_threshold && (attempt->count - attempt->dupes) >= settings.tempban_threshold)
		|| trashcan(cfg, name, "name")) && now < (time32_t)(attempt->time + settings.tempban_duration)) {
		if(details != NULL)
			*details = *attempt;
rswindell's avatar
rswindell committed
		return settings.tempban_duration - (now - attempt->time);
rswindell's avatar
rswindell committed
/****************************************************************************/
/* Message-new-scan pointer/configuration functions							*/
rswindell's avatar
rswindell committed
/****************************************************************************/
bool getmsgptrs(scfg_t* cfg, user_t* user, subscan_t* subscan, void (*progress)(void*, int, int), void* cbdata)
rswindell's avatar
rswindell committed
{
	char		path[MAX_PATH+1];
rswindell's avatar
rswindell committed
	int 		file;
	long		length;
	FILE*		stream;

	/* Initialize to configured defaults */
	for(i=0;i<cfg->total_subs;i++) {