Skip to content
Snippets Groups Projects
userdat.c 114 KiB
Newer Older
		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))!=0)
		return(err);

	if((err=putuserdat(cfg,user))!=0)
		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);
	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++) {
Rob Swindell's avatar
Rob Swindell committed
		FILE* fp = fopen_dstats(cfg, i ? cfg->node_num : 0, /* for_write: */TRUE);
		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);
		}
		fclose_dstats(fp);
	return(0);
}
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_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, uint subnum, user_t* user, client_t* client)
{
	if(!VALID_CFG(cfg))
		return FALSE;
	if(subnum>=cfg->total_subs)
		return FALSE;
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))

	return TRUE;
}

/****************************************************************************/
/* Determine if the specified user can or cannot read the specified sub		*/
/****************************************************************************/
BOOL can_user_read_sub(scfg_t* cfg, uint subnum, user_t* user, client_t* client)
{
	if(!can_user_access_sub(cfg, subnum, user, client))
		return FALSE;
	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, uint subnum, user_t* user, client_t* client, uint* reason)
{
	if(reason!=NULL)
		*reason=NoAccessSub;
	if(!can_user_access_sub(cfg, subnum, user, client))
		return FALSE;
	if(reason!=NULL)
		*reason=CantPostOnSub;
rswindell's avatar
rswindell committed
	if(!chk_ar(cfg,cfg->sub[subnum]->post_ar,user,client))
		return FALSE;
	if(cfg->sub[subnum]->misc&(SUB_QNET|SUB_FIDO|SUB_PNET|SUB_INET)
		&& user->rest&FLAG('N'))		/* network restriction? */
		return FALSE;
	if((cfg->sub[subnum]->misc & SUB_NAME)
		&& (user->rest & (FLAG('Q') | FLAG('O'))) == FLAG('O'))
		return FALSE;
	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])
		return FALSE;

	return TRUE;
}

/****************************************************************************/
// Determine if the specified user can access one or more directories of lib
/****************************************************************************/
BOOL can_user_access_lib(scfg_t* cfg, uint libnum, user_t* user, client_t* client)
{
	uint count = 0;

	for(uint 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(uint libnum = 0; libnum < cfg->total_libs; libnum++) {
		if(!can_user_access_lib(cfg, libnum, user, client))
			return FALSE;
	}
	return TRUE;
}

/****************************************************************************/
// Determine if the specified user can all dirs of a lib
/****************************************************************************/
BOOL can_user_access_all_dirs(scfg_t* cfg, uint libnum, user_t* user, client_t* client)
{
	uint count = 0;

	for(uint 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 FALSE;
	}
	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, uint dirnum, user_t* user, client_t* client)
{
	if(!VALID_CFG(cfg))
		return FALSE;
	if(dirnum>=cfg->total_dirs)
		return FALSE;
	if(!chk_ar(cfg,cfg->lib[cfg->dir[dirnum]->lib]->ar,user,client))
		return FALSE;
	if(!chk_ar(cfg,cfg->dir[dirnum]->ar,user,client))
		return FALSE;

	return TRUE;
}

/****************************************************************************/
/* 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, uint dirnum, user_t* user, client_t* client, uint* reason)
{
	if(reason!=NULL)
		*reason=NoAccessDir;
	if(!can_user_access_dir(cfg, dirnum, user, client))
		return FALSE;
	if(reason!=NULL)
		*reason=R_Upload;
	if(user->rest&FLAG('U'))			/* upload restriction? */
		return FALSE;
	if(user->rest&FLAG('T'))			/* transfer restriction? */
		return FALSE;
	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))
			return FALSE;
		if(!chk_ar(cfg, cfg->dir[dirnum]->ul_ar, user, client))
			return FALSE;
	}
	return TRUE;
}

/****************************************************************************/
/* 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, uint dirnum, user_t* user, client_t* client, uint* reason)
{
	if(reason!=NULL)
		*reason=NoAccessDir;
	if(!can_user_access_dir(cfg, dirnum, user, client))
		return FALSE;
	if(reason!=NULL)
		*reason=CantDownloadFromDir;
	if(!chk_ar(cfg,cfg->lib[cfg->dir[dirnum]->lib]->dl_ar,user,client))
		return FALSE;
	if(!chk_ar(cfg,cfg->dir[dirnum]->dl_ar,user,client))
		return FALSE;
	if(reason!=NULL)
		*reason=R_Download;
	if(user->rest&FLAG('D'))			/* download restriction? */
		return FALSE;
	if(user->rest&FLAG('T'))			/* transfer restriction? */
		return FALSE;

	return TRUE;
}

/****************************************************************************/
/* 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)
		return FALSE;
rswindell's avatar
rswindell committed
	if(net_type==NET_NONE && usernumber>1 && user->rest&FLAG('E'))			/* local mail restriction? */
		return FALSE;
	if(reason!=NULL)
		*reason=NoNetMailAllowed;
rswindell's avatar
rswindell committed
	if(net_type!=NET_NONE && user->rest&FLAG('M'))							/* netmail restriction */
		return FALSE;
	if(net_type==NET_FIDO && !(cfg->netmail_misc&NMAIL_ALLOW))				/* Fido netmail globally disallowed */
		return FALSE;
	if(net_type==NET_INTERNET && !(cfg->inetmail_misc&NMAIL_ALLOW))			/* Internet mail globally disallowed */
		return FALSE;
	if(reason!=NULL)
		*reason=R_Feedback;
rswindell's avatar
rswindell committed
	if(net_type==NET_NONE && usernumber==1 && user->rest&FLAG('S'))			/* feedback restriction? */
		return FALSE;
	if(reason!=NULL)
		*reason=TooManyEmailsToday;
	if(user->etoday>=cfg->level_emailperday[user->level] && !(user->exempt&FLAG('M')))
		return FALSE;

	return TRUE;
}

/****************************************************************************/
/* Determine if the specified user is a system operator						*/
/****************************************************************************/
BOOL is_user_sysop(user_t* user)
{
	if(user == NULL)
		return FALSE;
	return user->level >= SYSOP_LEVEL;
}

/****************************************************************************/
/* Determine if the specified user is a sub-board operator					*/
/****************************************************************************/
BOOL is_user_subop(scfg_t* cfg, uint subnum, user_t* user, client_t* client)
	if(!can_user_access_sub(cfg, subnum, user, client))
		return FALSE;
	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, uint dirnum, user_t* user, client_t* client)
{
	if(user==NULL)
		return FALSE;
	if(!can_user_access_dir(cfg, dirnum, user, client))
		return FALSE;
	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, uint dirnum, user_t* user, client_t* client)
	if(dirnum>=cfg->total_dirs)
		return(FALSE);

	if(cfg->dir[dirnum]->misc&DIR_FREE)
		return(TRUE);

	if(user==NULL)
		return(FALSE);

	if(user->exempt&FLAG('D'))
		return(TRUE);

	if(cfg->lib[cfg->dir[dirnum]->lib]->ex_ar[0] != 0
		&& chk_ar(cfg,cfg->lib[cfg->dir[dirnum]->lib]->ex_ar,user,client))
		return TRUE;

	if(cfg->dir[dirnum]->ex_ar[0]==0)
rswindell's avatar
rswindell committed
	return(chk_ar(cfg,cfg->dir[dirnum]->ex_ar,user,client));
BOOL is_host_exempt(scfg_t* cfg, const char* ip_addr, const char* host_name)
{
	char	exempt[MAX_PATH+1];

	SAFEPRINTF2(exempt, "%s%s", cfg->ctrl_dir, strIpFilterExemptConfigFile);
	return findstr(ip_addr, exempt) || findstr(host_name, exempt);
}

/****************************************************************************/
/* Add an IP address (with comment) to the IP filter/trashcan file			*/
/* ToDo: Move somewhere more appropriate (filter.c?)						*/
/****************************************************************************/
BOOL filter_ip(scfg_t* cfg, const char* prot, const char* reason, const char* host
					   ,const char* ip_addr, const char* username, const char* fname)
	if(ip_addr==NULL)
		return(FALSE);

	SAFEPRINTF2(exempt, "%s%s", cfg->ctrl_dir, strIpFilterExemptConfigFile);
	if(findstr(ip_addr, exempt))
		return(FALSE);
	if(findstr(host, exempt))
		return(FALSE);

	SAFEPRINTF(ip_can,"%sip.can",cfg->text_dir);
rswindell's avatar
rswindell committed
	if(findstr(ip_addr, fname))	/* Already filtered? */
		return(TRUE);

    fprintf(fp, "\n; %s %s ", prot, reason);
	if(username != NULL)
		fprintf(fp, "by %s ", username);
    fprintf(fp,"on %.24s\n", ctime_r(&now, tstr));

	if(host!=NULL)
		fprintf(fp,"; Hostname: %s\n",host);
	fprintf(fp,"%s\n",ip_addr);

/****************************************************************************/
/* 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)
		return FALSE;

	len=strlen(name);
	if(len<1)
		return FALSE;
	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)
{
	if(name == NULL)
		return FALSE;

	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		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)
		listPushNodeData(list, attempt, sizeof(login_attempt_t));
	listUnlock(list);

	return count;
#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;
	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
		&& findstr(ip_addr, exempt))
		return 0;
	if(host_name != NULL
		&& findstr(host_name, exempt))
		return 0;

	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;
	if(((settings.tempban_threshold && (attempt->count - attempt->dupes) >= settings.tempban_threshold)
		|| trashcan(cfg, attempt->user, "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];
	uint		i;
	int 		file;
	long		length;
	FILE*		stream;

	/* Initialize to configured defaults */
	for(i=0;i<cfg->total_subs;i++) {
		subscan[i].ptr=subscan[i].sav_ptr=0;
		subscan[i].last=subscan[i].sav_last=0;
		subscan[i].cfg=0xff;
		if(!(cfg->sub[i]->misc&SUB_NSDEF))
			subscan[i].cfg&=~SUB_CFG_NSCAN;
		if(!(cfg->sub[i]->misc&SUB_SSDEF))
			subscan[i].cfg&=~SUB_CFG_SSCAN;
		subscan[i].sav_cfg=subscan[i].cfg;
	if(user->number == 0)
		return 0;

	if(user->rest&FLAG('G'))
		return initmsgptrs(cfg, subscan, cfg->guest_msgscan_init, progress, cbdata);

	/* New way: */
	SAFEPRINTF2(path,"%suser/%4.4u.subs", cfg->data_dir, user->number);
	FILE* fp = fnopen(NULL, path, O_RDONLY|O_TEXT);
	if (fp != NULL) {
		str_list_t ini = iniReadFile(fp);
		for(i = 0; i < cfg->total_subs; i++) {
			if(progress != NULL)
				progress(cbdata, i, cfg->total_subs);
			str_list_t keys = iniGetSection(ini, cfg->sub[i]->code);
			if(keys == NULL)
				continue;
			subscan[i].ptr	= iniGetUInt32(keys, NULL, "ptr"	, subscan[i].ptr);
			subscan[i].last	= iniGetUInt32(keys, NULL, "last"	, subscan[i].last);
			subscan[i].cfg	= iniGetShortInt(keys, NULL, "cfg"	, subscan[i].cfg);
			subscan[i].cfg &= (SUB_CFG_NSCAN|SUB_CFG_SSCAN|SUB_CFG_YSCAN);	// Sanitize the 'cfg' value
			subscan[i].sav_ptr	= subscan[i].ptr;
			subscan[i].sav_last	= subscan[i].last;
			subscan[i].sav_cfg	= subscan[i].cfg;
			iniRemoveSection(&ini, cfg->sub[i]->code);
		}
		iniFreeStringList(ini);
		fclose(fp);
		if(progress != NULL)
			progress(cbdata, i, cfg->total_subs);
		return TRUE;
	}
	SAFEPRINTF2(path,"%suser/ptrs/%4.4u.ixb", cfg->data_dir, user->number);
rswindell's avatar
rswindell committed
	if((stream=fnopen(&file,path,O_RDONLY))==NULL) {
		if(fexist(path))
			return(FALSE);	/* file exists, but couldn't be opened? */
		return initmsgptrs(cfg, subscan, cfg->new_msgscan_init, progress, cbdata);
rswindell's avatar
rswindell committed
	}

	length=(long)filelength(file);
	for(i=0;i<cfg->total_subs;i++) {
rswindell's avatar
rswindell committed
		if(length>=(cfg->sub[i]->ptridx+1)*10L) {
			fseek(stream,(long)cfg->sub[i]->ptridx*10L,SEEK_SET);
			fread(&subscan[i].ptr,sizeof(subscan[i].ptr),1,stream);
			fread(&subscan[i].last,sizeof(subscan[i].last),1,stream);
			fread(&subscan[i].cfg,sizeof(subscan[i].cfg),1,stream);
		}
		subscan[i].sav_ptr=subscan[i].ptr;
		subscan[i].sav_last=subscan[i].last;
		subscan[i].sav_cfg=subscan[i].cfg;
	if(progress != NULL)
		progress(cbdata, i, cfg->total_subs);
rswindell's avatar
rswindell committed
	fclose(stream);
	return(TRUE);
}

/****************************************************************************/
/* Writes to data/user/####.subs the msgptr array for the current user		*/
/* Pass usernumber value of 0 to indicate "Guest" login						*/
rswindell's avatar
rswindell committed
/****************************************************************************/
BOOL putmsgptrs(scfg_t* cfg, user_t* user, subscan_t* subscan)
rswindell's avatar
rswindell committed
{
	char		path[MAX_PATH+1];
	if(user->number==0 || (user->rest&FLAG('G')))	/* Guest */
	SAFEPRINTF2(path,"%suser/%4.4u.subs", cfg->data_dir, user->number);
	FILE* fp = fnopen(NULL, path, O_RDWR|O_CREAT|O_TEXT);
	if (fp == NULL)
		return FALSE;
rswindell's avatar
rswindell committed
	ini_style_t ini_style = { .key_prefix = "\t", .section_separator = "" };
	BOOL modified = FALSE;
	for(i=0; i < cfg->total_subs; i++) {
		str_list_t keys = iniGetSection(ini, cfg->sub[i]->code);
		if(subscan[i].sav_ptr==subscan[i].ptr
rswindell's avatar
rswindell committed
			&& subscan[i].sav_last==subscan[i].last
			&& subscan[i].sav_cfg==subscan[i].cfg
			&& keys != NULL && *keys != NULL)
			iniAppendSectionWithKeys(&new, cfg->sub[i]->code, keys, &ini_style);
		else {
			iniSetUInt32(&new, cfg->sub[i]->code, "ptr", subscan[i].ptr, &ini_style);
			iniSetUInt32(&new, cfg->sub[i]->code, "last", subscan[i].last, &ini_style);
			iniSetHexInt(&new, cfg->sub[i]->code, "cfg", subscan[i].cfg, &ini_style);
			iniSetDateTime(&new, cfg->sub[i]->code, "updated", /* include_time: */TRUE, now, &ini_style);
			modified = TRUE;
		}
		if(keys != NULL) {
			iniRemoveSection(&ini, cfg->sub[i]->code);
			iniFreeStringList(keys);
		}
	}
	if(modified || strListCount(ini))
		result = iniWriteFile(fp, new);
	strListFree(&new);
BOOL newmsgs(smb_t* smb, time_t t)
{
	char index_fname[MAX_PATH + 1];

	SAFEPRINTF(index_fname, "%s.sid", smb->file);
	return fdate(index_fname) >= t;
}

rswindell's avatar
rswindell committed
/****************************************************************************/
/* Initialize new-msg-scan pointers (e.g. for new users)					*/
/* If 'days' is specified as 0, just set pointer to last message (faster)	*/
/****************************************************************************/
BOOL initmsgptrs(scfg_t* cfg, subscan_t* subscan, unsigned days, void (*progress)(void*, int, int), void* cbdata)
rswindell's avatar
rswindell committed
{
	uint		i;
	smb_t		smb;
	idxrec_t	idx;
	time_t		t = time(NULL) - (days * 24 * 60 * 60);

	for(i=0;i<cfg->total_subs;i++) {
		/* This value will be "fixed" (changed to the last msg) when saving */
		subscan[i].ptr = ~0;
		if(days == 0)
rswindell's avatar
rswindell committed
		ZERO_VAR(smb);
		SAFEPRINTF2(smb.file,"%s%s",cfg->sub[i]->data_dir,cfg->sub[i]->code);
rswindell's avatar
rswindell committed
		smb.retry_time=cfg->smb_retry_time;
		smb.subnum=i;
		if(smb_open_index(&smb) != SMB_SUCCESS)
rswindell's avatar
rswindell committed
			continue;
		memset(&idx, 0, sizeof(idx));
		smb_getlastidx(&smb, &idx);
		subscan[i].ptr = idx.number;
		if(idx.time >= t && smb_getmsgidx_by_time(&smb, &idx, t) >= SMB_SUCCESS)
	if(progress != NULL)
		progress(cbdata, i, cfg->total_subs);
	return TRUE;
}

/****************************************************************************/
/* Insure message new-scan pointers are within the range of the msgs in		*/
/* the sub-board.															*/
/****************************************************************************/
BOOL fixmsgptrs(scfg_t* cfg, subscan_t* subscan)
{
	uint		i;
	smb_t		smb;

	for(i=0;i<cfg->total_subs;i++) {
		if(subscan[i].ptr == 0)
			continue;
		if(subscan[i].ptr < ~0 && subscan[i].sav_ptr == subscan[i].ptr)
			continue;
		ZERO_VAR(smb);
		SAFEPRINTF2(smb.file,"%s%s",cfg->sub[i]->data_dir,cfg->sub[i]->code);
		smb.retry_time=cfg->smb_retry_time;
		smb.subnum=i;
		if(smb_open_index(&smb) != SMB_SUCCESS) {
			subscan[i].ptr = 0;
		smb_getlastidx(&smb, &idx);
		if(subscan[i].ptr > idx.number)
			subscan[i].ptr = idx.number;
		if(subscan[i].last > idx.number)
			subscan[i].last = idx.number;
rswindell's avatar
rswindell committed
		smb_close(&smb);
	}
	return TRUE;
}

static char* sysop_available_semfile(scfg_t* scfg)
{
	static char semfile[MAX_PATH+1];
	SAFEPRINTF(semfile, "%ssysavail.chat", scfg->ctrl_dir);
	return semfile;
}

BOOL set_sysop_availability(scfg_t* scfg, BOOL available)
{
	if(available)
		return ftouch(sysop_available_semfile(scfg));
	return remove(sysop_available_semfile(scfg)) == 0;
}
static char* sound_muted_semfile(scfg_t* scfg)
{
	static char semfile[MAX_PATH+1];
	SAFEPRINTF(semfile, "%ssound.mute", scfg->ctrl_dir);
	return semfile;
}

BOOL sound_muted(scfg_t* scfg)
{
	return fexist(sound_muted_semfile(scfg));
}