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

/****************************************************************************
 * @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										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "str_util.h"
#include "cmdshell.h"
#include "userdat.h"
#include "filedat.h"
#include "ars_defs.h"
#include "text.h"
#include "nopen.h"
#include "datewrap.h"
#include "date_str.h"
#include "smblib.h"
#include "getstats.h"
#include "msgdate.h"
#include "xpdatetime.h"
#ifndef USHRT_MAX
	#define USHRT_MAX ((unsigned short)~0)
#define VALID_CFG(cfg)	(cfg!=NULL && cfg->size==sizeof(scfg_t))
#define VALID_USER_NUMBER(n) ((n) >= 1)
#define VALID_USER_FIELD(n)	((n) >= 0 && (n) < USER_FIELD_COUNT)
#define USER_FIELD_SEPARATOR '\t'
static const char user_field_separator[2] = { USER_FIELD_SEPARATOR, '\0' };

#define LOOP_USERDAT 500
char* userdat_filename(scfg_t* cfg, char* path, size_t size)
{
	safe_snprintf(path, size, "%suser/" USER_DATA_FILENAME, cfg->data_dir);
char* msgptrs_filename(scfg_t* cfg, unsigned user_number, char* path, size_t size)
{
	safe_snprintf(path, size, "%suser/%4.4u.subs", cfg->data_dir, user_number);
	return path;
}

/****************************************************************************/
/****************************************************************************/
void split_userdat(char *userdat, char* field[])
{
	char* p = userdat;
	for(size_t i = 0; i < USER_FIELD_COUNT; i++) {
		field[i] = p;
		FIND_CHAR(p, USER_FIELD_SEPARATOR);
		if(*p != '\0')
			*(p++) = '\0';
	}
}

/****************************************************************************/
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 matchuser(scfg_t* cfg, const char *name, bool sysop_alias)
	int		file,c;
	char	dat[LEN_ALIAS+2];
	char	str[256];
	off_t	l,length;
	if(!VALID_CFG(cfg) || name==NULL || *name == '\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)
	length = filelength(file);
	if(length < sizeof(dat)) {
		fclose(stream);
		return 0;
	}
	for(l = 0; l < length; l += sizeof(dat)) {
		if(fread(dat,sizeof(dat),1,stream) != 1)
			break;
		for(c=0;c<LEN_ALIAS;c++)
			if(dat[c]==ETX) break;
		dat[c]=0;
		if(c < 1) // Deleted user
			continue;
		if(matchusername(cfg, dat, name))
		return (uint)((l/(LEN_ALIAS+2))+1);
/****************************************************************************/
/* Return true if the user 'name' (or alias) matches with 'comp'			*/
/* ... ignoring non-alpha-numeric chars in either string					*/
/* and terminating the comparison string at an '@'							*/
/****************************************************************************/
bool matchusername(scfg_t* cfg, const char* name, const char* comp)
{
	(void)cfg; // in case we want this matching logic to be configurable in the future

	const char* np = name;
	const char* cp = comp;
	while(*np != '\0' && *cp != '\0' && *cp != '@') {
			cp++;
			continue;
		}
		if(toupper(*np) != toupper(*cp))
			break;
		np++, cp++;
	}
	while(*np != '\0' && !IS_ALPHANUMERIC(*np))
		np++;
	while(*cp != '\0' && !IS_ALPHANUMERIC(*cp) && *cp != '@' )
		cp++;
	return *np == '\0' && (*cp == '\0' || *cp == '@');
}

/****************************************************************************/
uint find_login_id(scfg_t* cfg, const char* user_id)
{
	uint usernum;

	if(cfg == NULL || user_id == NULL)
		return 0;
	if((cfg->sys_login & LOGIN_USERNUM) && IS_DIGIT(user_id[0])) {
		char* end = NULL;
		usernum = (uint)strtoul(user_id, &end, 10);
		if(end == NULL || *end != '\0' || usernum > (uint)lastuser(cfg))
	usernum = matchuser(cfg, user_id, /* sysop_alias: */false);
	if(usernum < 1 && check_realname(cfg, user_id) && (cfg->sys_login & LOGIN_REALNAME))
		usernum = finduserstr(cfg, 0, USER_NAME, user_id
			,/* del: */false, /* next: */false
			,/* Progress_cb: */NULL, /* cbdata: */NULL);
	return usernum;
}

/****************************************************************************/
	bool	success = true;
	if((file=openuserdat(cfg, /* for_modify: */false)) < 0)
	for(int usernumber = 1; success; usernumber++) {
		char userdat[USER_RECORD_LEN + 1];
		if(readuserdat(cfg, usernumber, userdat, sizeof(userdat), file, /* leave_locked: */false) == 0) {
			char* field[USER_FIELD_COUNT];
			split_userdat(userdat, field);
			if(!(ahtou32(field[USER_MISC]) & (DELETED|INACTIVE)))
				total_users++;
		} else
			success = false;
/****************************************************************************/
/* Returns the number of the last user in user.tab (deleted ones too)		*/
/****************************************************************************/
	char path[MAX_PATH + 1];
	off_t length;
	if(!VALID_CFG(cfg))
		return(0);

	if((length = flength(userdat_filename(cfg, path, sizeof(path)))) > 0)
		return (int)(length / USER_RECORD_LINE_LEN);
/****************************************************************************/
/* Deletes (completely removes) last user record in userbase				*/
/****************************************************************************/
bool del_lastuser(scfg_t* cfg)
{
	int		file;
	if(!VALID_CFG(cfg))
	if((file=openuserdat(cfg, /* for_modify: */true)) < 0)
		return(false);
	length = filelength(file);
	if(length < USER_RECORD_LINE_LEN) {
		close(file);
	int result = chsize(file, (long)length - USER_RECORD_LINE_LEN);
	close(file);
/****************************************************************************/
/* Opens the user database returning the file descriptor or -1 on error		*/
/****************************************************************************/
int openuserdat(scfg_t* cfg, bool for_modify)
	char path[MAX_PATH+1];
	if(!VALID_CFG(cfg))
	return nopen(userdat_filename(cfg, path, sizeof(path)), for_modify ? (O_RDWR|O_CREAT|O_DENYNONE) : (O_RDONLY|O_DENYNONE));
off_t userdatoffset(unsigned user_number)
{
	return (user_number - 1) * USER_RECORD_LINE_LEN;
bool seekuserdat(int file, unsigned user_number)
	return lseek(file, userdatoffset(user_number), SEEK_SET) == userdatoffset(user_number);
bool lockuserdat(int file, unsigned user_number)
{
	if(!VALID_USER_NUMBER(user_number))

	off_t offset = userdatoffset(user_number);

	if(lseek(file, offset, SEEK_SET) != offset)
	unsigned attempt=0;
	while(attempt < LOOP_USERDAT && lock(file, offset, USER_RECORD_LINE_LEN) == -1) {
		mswait((attempt / 10) * 100);
	}
	return attempt < LOOP_USERDAT;
}

bool unlockuserdat(int file, unsigned user_number)
{
	if(!VALID_USER_NUMBER(user_number))
	return unlock(file, userdatoffset(user_number), USER_RECORD_LINE_LEN) == 0;
/****************************************************************************/
/* Locks and reads a single user record from an open userbase file into a	*/
/* buffer of USER_RECORD_LINE_LEN in size.									*/
/* Returns 0 on success.													*/
/****************************************************************************/
int readuserdat(scfg_t* cfg, unsigned user_number, char* userdat, size_t size, int infile, bool leave_locked)
	if(!VALID_CFG(cfg) || !VALID_USER_NUMBER(user_number))
	memset(userdat, 0, size);
		file = infile;
	else {
		if((file = openuserdat(cfg, /* for_modify: */false)) < 0)
			return file;
	}
	if(user_number > (unsigned)(filelength(file) / USER_RECORD_LINE_LEN)) {
		if(file != infile)
			close(file);
		return -2;	/* no such user record */
	}

	if(!seekuserdat(file, user_number)) {
		if(file != infile)
			close(file);
		return -3;
	if(!lockuserdat(file, user_number)) {
		if(file != infile)
			close(file);
	if(read(file, userdat, size - 1) != size - 1) {
		unlockuserdat(file, user_number);
		if(file != infile)
			close(file);
	if(!leave_locked)
		unlockuserdat(file, user_number);
	if(file != infile)
		close(file);
	return 0;
}

// Assumes file already positioned at beginning of user record
bool writeuserfields(scfg_t* cfg, char* field[], int file)
	char	userdat[USER_RECORD_LINE_LEN + 1] = "";

	for(size_t i = 0; i < USER_FIELD_COUNT; i++) {
		SAFECAT(userdat, field[i]);
		SAFECAT(userdat, user_field_separator);
	memset(userdat + len, USER_FIELD_SEPARATOR, USER_RECORD_LEN - len);
	userdat[USER_RECORD_LINE_LEN - 1] = '\n';
	if(write(file, userdat, USER_RECORD_LINE_LEN) != USER_RECORD_LINE_LEN)
		return false;
	return true;
}

static time32_t parse_usertime(const char* str)
{
	time_t result = xpDateTime_to_time(isoDateTimeStr_parse(str));
	if(result == INVALID_TIME)
		result = 0;
	return (time32_t)result;
}

/****************************************************************************/
/* Fills the structure 'user' with info for user.number	from userdat		*/
/* (a buffer representing a single user 'record' from the userbase file		*/
/****************************************************************************/
int parseuserdat(scfg_t* cfg, char *userdat, user_t *user, char* field[])
{
	unsigned user_number;

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

	if(!VALID_CFG(cfg) || !VALID_USER_NUMBER(user_number))
	/* 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 */

	char* fbuf[USER_FIELD_COUNT];
	if(field == NULL)
		field = fbuf;
	split_userdat(userdat, field);
	SAFECOPY(user->alias, field[USER_ALIAS]);
	SAFECOPY(user->name, field[USER_NAME]);
	SAFECOPY(user->handle, field[USER_HANDLE]);
	SAFECOPY(user->note, field[USER_NOTE]);
	SAFECOPY(user->ipaddr, field[USER_IPADDR]);
	SAFECOPY(user->comp, field[USER_HOST]);
	SAFECOPY(user->netmail, field[USER_NETMAIL]);
	SAFECOPY(user->address, field[USER_ADDRESS]);
	SAFECOPY(user->location, field[USER_LOCATION]);
	SAFECOPY(user->zipcode, field[USER_ZIPCODE]);
	SAFECOPY(user->phone, field[USER_PHONE]);
	SAFECOPY(user->birth, field[USER_BIRTH]);
	user->sex = *field[USER_GENDER];
	SAFECOPY(user->comment, field[USER_COMMENT]);
	SAFECOPY(user->modem, field[USER_CONNECTION]);

	user->misc = (uint32_t)strtoul(field[USER_MISC], NULL, 16);
	user->qwk = (uint32_t)strtoul(field[USER_QWK], NULL, 16);
	user->chat = (uint32_t)strtoul(field[USER_CHAT], NULL, 16);
	user->mail = (uint32_t)strtoul(field[USER_MAIL], NULL, 16);

	user->rows = strtoul(field[USER_ROWS], NULL, 0);
	user->cols = strtoul(field[USER_COLS], NULL, 0);

	user->xedit = getxeditnum(cfg, field[USER_XEDIT]);
	user->shell = getshellnum(cfg, field[USER_SHELL]);

	SAFECOPY(user->tmpext, field[USER_TMPEXT]);
	user->prot = *field[USER_PROT];
	SAFECOPY(user->cursub, field[USER_CURSUB]);
	SAFECOPY(user->curdir, field[USER_CURDIR]);
	SAFECOPY(user->curxtrn, field[USER_CURXTRN]);
	user->logontime = parse_usertime(field[USER_LOGONTIME]);
	user->ns_time = parse_usertime(field[USER_NS_TIME]);
	user->laston = parse_usertime(field[USER_LASTON]);
	user->firston = parse_usertime(field[USER_FIRSTON]);

	user->logons = (ushort)strtoul(field[USER_LOGONS], NULL, 0);
	user->ltoday = (ushort)strtoul(field[USER_LTODAY], NULL, 0);
	user->timeon = (ushort)strtoul(field[USER_TIMEON], NULL, 0);
	user->ttoday = (ushort)strtoul(field[USER_TTODAY], NULL, 0);
	user->tlast = (ushort)strtoul(field[USER_TLAST], NULL, 0);
	user->posts = (ushort)strtoul(field[USER_POSTS], NULL, 0);
	user->emails = (ushort)strtoul(field[USER_EMAILS], NULL, 0);
	user->fbacks = (ushort)strtoul(field[USER_FBACKS], NULL, 0);
	user->etoday = (ushort)strtoul(field[USER_ETODAY], NULL, 0);
	user->ptoday = (ushort)strtoul(field[USER_PTODAY], NULL, 0);

	user->ulb = strtoull(field[USER_ULB], NULL, 0);
	user->uls = (ushort)strtoul(field[USER_ULS], NULL, 0);
	user->dlb = strtoull(field[USER_DLB], NULL, 0);
	user->dls = (ushort)strtoul(field[USER_DLS], NULL, 0);
	user->dlcps = (uint32_t)strtoul(field[USER_DLCPS], NULL, 0);
	user->leech = (uchar)strtoul(field[USER_LEECH], NULL, 0);

	SAFECOPY(user->pass, field[USER_PASS]);
	user->pwmod = parse_usertime(field[USER_PWMOD]);
	user->level = atoi(field[USER_LEVEL]);
	user->flags1 = aftou32(field[USER_FLAGS1]);
	user->flags2 = aftou32(field[USER_FLAGS2]);
	user->flags3 = aftou32(field[USER_FLAGS3]);
	user->flags4 = aftou32(field[USER_FLAGS4]);
	user->exempt = aftou32(field[USER_EXEMPT]);
	user->rest = aftou32(field[USER_REST]);
	user->cdt = strtoull(field[USER_CDT], NULL, 0);
	user->freecdt = strtoull(field[USER_FREECDT], NULL, 0);
	user->min = strtoul(field[USER_MIN], NULL, 0);
	user->textra = (ushort)strtoul(field[USER_TEXTRA], NULL, 0);
	user->expire = parse_usertime(field[USER_EXPIRE]);
	/* 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 userbase file	*/
/****************************************************************************/
int getuserdat(scfg_t* cfg, user_t *user)
{
	int		retval;
	int		file;
	char	userdat[USER_RECORD_LINE_LEN + 1];
	if(!VALID_CFG(cfg) || user==NULL || !VALID_USER_NUMBER(user->number))
	if((file = openuserdat(cfg, /* for_modify: */false)) < 0) {
		return file;
	if((retval = readuserdat(cfg, user->number, userdat, sizeof(userdat), file, /* leave_locked: */false)) != 0) {
		close(file);
		return retval;
	}
	retval = parseuserdat(cfg, userdat, user, NULL);
	close(file);
	return retval;
}

/* Fast getuserdat() (leaves userbase file open) */
int fgetuserdat(scfg_t* cfg, user_t *user, int file)
	char	userdat[USER_RECORD_LEN + 1];
	if(!VALID_CFG(cfg) || user==NULL || !VALID_USER_NUMBER(user->number))
	if((retval = readuserdat(cfg, user->number, userdat, sizeof(userdat), file, /* leave_locked: */false)) != 0) {
	return parseuserdat(cfg, userdat, user, NULL);
/****************************************************************************/
/****************************************************************************/
static void dirtyuserdat(scfg_t* cfg, uint usernumber)
{
    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, /* lockit: */false, &file) != 0)
		if(node.useron==usernumber && (node.status==NODE_INUSE
			|| node.status==NODE_QUIET)) {
			if(getnodedat(cfg, i,&node, /* lockit: */true, &file) == 0) {
				putnodedat(cfg, i,&node, /* closeit: */false, file);
/****************************************************************************/
/****************************************************************************/
int is_user_online(scfg_t* cfg, uint usernumber)
	node_t	node;

	for(i=1; i<=cfg->sys_nodes; i++) {
		getnodedat(cfg, i, &node, /* lockit: */false, &file);
		if((node.status==NODE_INUSE || node.status==NODE_QUIET
			|| node.status==NODE_LOGON) && node.useron==usernumber)
// Returns empty string if 't' is zero (Unix epoch)
static char* format_datetime(time_t t, char* str, size_t size)
		gmtime_to_isoDateTimeStr(t, str, size);
/****************************************************************************/
/****************************************************************************/
bool format_userdat(scfg_t* cfg, user_t* user, char userdat[])
	if(!VALID_CFG(cfg) || !VALID_USER_NUMBER(user->number))
	char flags1[LEN_FLAGSTR + 1];
	char flags2[LEN_FLAGSTR + 1];
	char flags3[LEN_FLAGSTR + 1];
	char flags4[LEN_FLAGSTR + 1];
	char exemptions[LEN_FLAGSTR + 1];
	char restrictions[LEN_FLAGSTR + 1];
	char logontime[64];
	char ns_time[64];
	char firston[64];
	char laston[64];
	char pwmod[64];
	char expire[64];
	u32toaf(user->flags1, flags1);
	u32toaf(user->flags2, flags2);
	u32toaf(user->flags3, flags3);
	u32toaf(user->flags4, flags4);
	u32toaf(user->exempt, exemptions);
	u32toaf(user->rest, restrictions);
	format_datetime(user->logontime, logontime, sizeof(logontime));
	format_datetime(user->ns_time, ns_time, sizeof(ns_time));
	format_datetime(user->firston, firston, sizeof(firston));
	format_datetime(user->laston, laston, sizeof(laston));
	format_datetime(user->pwmod, pwmod, sizeof(pwmod));
	format_datetime(user->expire, expire, sizeof(expire));

	// NOTE: order must match enum user_field definition (in userfields.h)
	int len = snprintf(userdat, USER_RECORD_LEN,
		"%u\t"	// USER_ID
		"%s\t"	// USER_ALIAS
		"%s\t"	// USER_NAME
		"%s\t"	// USER_HANDLE
		"%s\t"	// USER_NOTE
		"%s\t"	// USER_IPADDR
		"%s\t"	// USER_HOST
		"%s\t"	// USER_NETMAIL
		"%s\t"	// USER_ADDRESS
		"%s\t"	// USER_LOCATION
		"%s\t"	// USER_ZIPCODE
		"%s\t"	// USER_PHONE
		"%s\t"	// USER_BIRTH
		"%c\t"	// USER_GENDER
		"%s\t"	// USER_COMMENT
		"%s\t"	// USER_CONNECTION
		"%x\t"	// USER_MISC
		"%x\t"	// USER_QWK
		"%x\t"	// USER_CHAT
		"%u\t"	// USER_ROWS
		"%u\t"	// USER_COLS
		"%s\t"	// USER_XEDIT
		"%s\t"	// USER_SHELL
		"%s\t"	// USER_TMPEXT
		"%c\t"	// USER_PROT
		"%s\t"	// USER_CURSUB
		"%s\t"	// USER_CURDIR
		"%s\t"	// USER_CURXTRN
		"%s\t"	// USER_LOGONTIME
		"%s\t"	// USER_NS_TIME
		"%s\t"	// USER_LASTON
		"%s\t"	// USER_FIRSTON
		"%u\t"	// USER_LOGONS
		"%u\t"	// USER_LTODAY
		"%u\t"	// USER_TIMEON
		"%u\t"	// USER_TTODAY
		"%u\t"	// USER_TLAST
		"%u\t"	// USER_POSTS
		"%u\t"	// USER_EMAILS
		"%u\t"	// USER_FBACKS
		"%u\t"	// USER_ETODAY
		"%u\t"	// USER_PTODAY
		"%" PRIu64 "\t"	// USER_ULB
		"%u\t"			// USER_ULS
		"%" PRIu64 "\t" // USER_DLB
		"%u\t"			// USER_DLS
		"%" PRIu32 "\t" // USER_DLCPS
		"%s\t"	// USER_PASS
		"%u\t"	// USER_LEVEL
		"%s\t"	// USER_FLAGS1
		"%s\t"	// USER_FLAGS2
		"%s\t"	// USER_FLAGS3
		"%s\t"	// USER_FLAGS4
		"%s\t"	// USER_EXEMPT
		"%s\t"	// USER_REST
		"%" PRIu64 "\t"	// USER_CDT
		"%" PRIu64 "\t"	// USER_FREECDT
		"%" PRIu32 "\t" // USER_MIN
		"%u\t"	// USER_TEXTRA
		"%u\t"	// USER_LEECH
		,user->number
		,user->alias
		,user->name
		,user->handle
		,user->note
		,user->ipaddr
		,user->comp
		,user->netmail
		,user->address
		,user->location
		,user->zipcode
		,user->phone
		,user->birth
		,user->comment
		,user->modem
		,user->misc
		,user->qwk
		,user->chat
		,user->rows
		,user->cols
		,user->xedit && user->xedit <= cfg->total_xedits ? cfg->xedit[user->xedit - 1]->code : ""
		,user->shell < cfg->total_shells ? cfg->shell[user->shell]->code : ""
		,user->cursub
		,user->curdir
		,user->curxtrn
		,logontime
		,ns_time
		,laston
		,firston
		,(uint)user->ltoday
		,(uint)user->timeon
		,(uint)user->ttoday
		,(uint)user->tlast
		,(uint)user->posts
		,(uint)user->emails
		,(uint)user->fbacks
		,(uint)user->etoday
		,(uint)user->ptoday
		,user->dlcps
		,flags1
		,flags2
		,flags3
		,flags4
		,exemptions
		,restrictions
		,user->cdt
		,user->freecdt
		,user->min
		,user->textra
		,(uint)user->leech
	if(len > USER_RECORD_LEN || len < 0) // truncated?
	memset(userdat + len, USER_FIELD_SEPARATOR, USER_RECORD_LEN - len);
	userdat[USER_RECORD_LINE_LEN - 1] = '\n';
}

/****************************************************************************/
/* Writes into user.number's slot in userbase data in structure 'user'      */
/* Called from functions newuser, useredit and main                         */
/****************************************************************************/
int putuserdat(scfg_t* cfg, user_t* user)
{
    int		file;
    char	userdat[USER_RECORD_LINE_LEN];
	if(!VALID_CFG(cfg) || !VALID_USER_NUMBER(user->number))
		return(-1);

	if(!format_userdat(cfg, user, userdat))
		return -10;
	if((file=openuserdat(cfg, /* for_modify: */true)) < 0)
	if(filelength(file)<((off_t)user->number - 1) * USER_RECORD_LINE_LEN) {
	seekuserdat(file, user->number);
	if(!lockuserdat(file, user->number)) {
	if(write(file,userdat,sizeof(userdat)) != sizeof(userdat)) {
		unlockuserdat(file, user->number);
	unlockuserdat(file, user->number);
	return(0);
}

/****************************************************************************/
/* Returns the username in 'str' that corresponds to the 'usernumber'       */
/* Called from functions everywhere                                         */
/****************************************************************************/
char* username(scfg_t* cfg, int usernumber, char *name)
	if(!VALID_CFG(cfg) || !VALID_USER_NUMBER(usernumber)) {
	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);
	(void)lseek(file,(long)((long)(usernumber-1)*(LEN_ALIAS+2)),SEEK_SET);
	if(read(file,name,LEN_ALIAS) != LEN_ALIAS)
		memset(name, ETX, LEN_ALIAS);
	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 putusername(scfg_t* cfg, int number, const char *name)
	int wr;
	off_t length;
	off_t total_users;
	if(!VALID_CFG(cfg) || name==NULL || !VALID_USER_NUMBER(number))
	SAFEPRINTF(str,"%suser/name.dat", cfg->data_dir);
	if((file=nopen(str,O_RDWR|O_CREAT))==-1)
		return(errno);
	length = filelength(file);

	/* Truncate corrupted name.dat */
	total_users=lastuser(cfg);
	if(length/(LEN_ALIAS+2) > total_users) {
		if(chsize(file,(long)(total_users*(LEN_ALIAS+2))) != 0) {
			close(file);
			return -4;
		}
	}
	if(length && length%(LEN_ALIAS+2)) {
		close(file);
	}
	if(length<(((long)number-1)*(LEN_ALIAS+2))) {
		SAFEPRINTF2(str,"%*s\r\n",LEN_ALIAS,"");
		memset(str,ETX,LEN_ALIAS);
		(void)lseek(file,0L,SEEK_END);
		while((length = filelength(file)) >= 0 && length < ((long)number*(LEN_ALIAS+2)))	// Shouldn't this be (number-1)?
			if(write(file,str,(LEN_ALIAS+2)) != LEN_ALIAS+2)
				break;
	(void)lseek(file,(long)(((long)number-1)*(LEN_ALIAS+2)),SEEK_SET);
	putrec(str,0,LEN_ALIAS,name);
	wr=write(file,str,LEN_ALIAS+2);
	if(wr!=LEN_ALIAS+2)
		return(errno);
#define DECVAL(ch, mul)	(DEC_CHAR_TO_INT(ch) * (mul))

int getbirthyear(const char* birth)
Rob Swindell's avatar
Rob Swindell committed
	if(IS_DIGIT(birth[2]))				// CCYYMMDD format
		return DECVAL(birth[0], 1000)
				+ DECVAL(birth[1], 100)
				+ DECVAL(birth[2], 10)
				+ DECVAL(birth[3], 1);
	// DD/MM/YY or MM/DD/YY format
	time_t now = time(NULL);
	struct	tm tm;
	if(localtime_r(&now, &tm) == NULL)
		return 0;
	tm.tm_year += 1900;
	int year = 1900 + DECVAL(birth[6], 10) + DECVAL(birth[7], 1);
	if(tm.tm_year - year > 105)
		year += 100;
	return year;
}

int getbirthmonth(scfg_t* cfg, const char* birth)
Rob Swindell's avatar
Rob Swindell committed
	if(IS_DIGIT(birth[5]))				// CCYYMMDD format
		return DECVAL(birth[4], 10)	+ DECVAL(birth[5], 1);
	if(cfg->sys_misc & SM_EURODATE) {	// DD/MM/YY format
		return DECVAL(birth[3], 10) + DECVAL(birth[4], 1);
	} else {							// MM/DD/YY format
		return DECVAL(birth[0], 10) + DECVAL(birth[1], 1);
	}
}

int getbirthday(scfg_t* cfg, const char* birth)
Rob Swindell's avatar
Rob Swindell committed
	if(IS_DIGIT(birth[5]))				// CCYYMMDD format
		return DECVAL(birth[6], 10)	+ DECVAL(birth[7], 1);
	if(cfg->sys_misc & SM_EURODATE) {	// DD/MM/YY format
		return DECVAL(birth[0], 10) + DECVAL(birth[1], 1);
	} else {							// MM/DD/YY format
		return DECVAL(birth[3], 10) + DECVAL(birth[4], 1);
	}
}

// Always returns string in MM/DD/YY format
char* getbirthmmddyy(scfg_t* cfg, char sep, const char* birth, char* buf, size_t max)
	safe_snprintf(buf, max, "%02u%c%02u%c%02u"
		, getbirthyear(birth) % 100);
	return buf;
}

// Always returns string in DD/MM/YY format
char* getbirthddmmyy(scfg_t* cfg, char sep, const char* birth, char* buf, size_t max)
	safe_snprintf(buf, max, "%02u%c%02u%c%02u"
		, getbirthyear(birth) % 100);
	return buf;
}

// Always returns string in YY/MM/DD format
char* getbirthyymmdd(scfg_t* cfg, char sep, const char* birth, char* buf, size_t max)
	safe_snprintf(buf, max, "%02u%c%02u%c%02u"
		, getbirthyear(birth) % 100
		, getbirthmonth(cfg, birth)
		, getbirthday(cfg, birth));
	return buf;
}

char* getbirthdstr(scfg_t* cfg, const char* birth, char* buf, size_t max)
{
	if(cfg->sys_date_fmt == YYMMDD)
		getbirthyymmdd(cfg, cfg->sys_date_sep, birth, buf, max);
	else if(cfg->sys_date_fmt == DDMMYY)
		getbirthddmmyy(cfg, cfg->sys_date_sep, birth, buf, max);
		getbirthmmddyy(cfg, cfg->sys_date_sep, birth, buf, max);
/****************************************************************************/
/* Returns the age derived from the string 'birth' in the format CCYYMMDD	*/
/* or legacy: MM/DD/YY or DD/MM/YY											*/
/* Returns 0 on invalid 'birth' date										*/
/****************************************************************************/
int getage(scfg_t* cfg, const char *birth)
	if(!VALID_CFG(cfg) || birth==NULL)
		return(0);