userdat.c 105 KB
Newer Older
1 2 3 4 5 6
/* 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)		*
 *																			*
rswindell's avatar
rswindell committed
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *																			*
 * 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.	*
 ****************************************************************************/

22
#include "str_util.h"
23
#include "cmdshell.h"
24 25 26 27 28 29 30 31 32 33
#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"
34
#include "scfglib.h"
35

36
#ifndef USHRT_MAX
deuce's avatar
deuce committed
37
	#define USHRT_MAX ((unsigned short)~0)
38
#endif
39 40

/* convenient space-saving global variables */
41 42
static const char* crlf="\r\n";
static const char* nulstr="";
43 44
static const char* strIpFilterExemptConfigFile = "ipfilter_exempt.cfg";

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

47
/****************************************************************************/
rswindell's avatar
rswindell committed
48 49
/* Looks for a perfect match among all usernames (not deleted users)		*/
/* Makes dots and underscores synonymous with spaces for comparisons		*/
50 51
/* Returns the number of the perfect matched username or 0 if no match		*/
/****************************************************************************/
52
uint matchuser(scfg_t* cfg, const char *name, BOOL sysop_alias)
53
{
54 55 56 57 58
	int		file,c;
	char	dat[LEN_ALIAS+2];
	char	str[256];
	ulong	l,length;
	FILE*	stream;
59

60 61 62
	if(!VALID_CFG(cfg) || name==NULL)
		return(0);

63 64
	if(sysop_alias &&
		(!stricmp(name,"SYSOP") || !stricmp(name,"POSTMASTER") || !stricmp(name,cfg->sys_id)))
65
		return(1);
66

67
	SAFEPRINTF(str,"%suser/name.dat",cfg->data_dir);
68
	if((stream=fnopen(&file,str,O_RDONLY))==NULL)
69
		return(0);
70
	length=(long)filelength(file);
71
	for(l=0;l<length;l+=LEN_ALIAS+2) {
72
		fread(dat,sizeof(dat),1,stream);
73
		for(c=0;c<LEN_ALIAS;c++)
74 75
			if(dat[c]==ETX) break;
		dat[c]=0;
76
		if(matchusername(cfg, dat, name))
77
			break;
78 79
	}
	fclose(stream);
80
	if(l<length)
81
		return((l/(LEN_ALIAS+2))+1);
82 83 84
	return(0);
}

85 86 87 88 89 90 91 92 93 94 95 96
/****************************************************************************/
/* 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 != '@') {
97
		if(!IS_ALPHANUMERIC(*np)) {
98 99 100
			np++;
			continue;
		}
101
		if(!IS_ALPHANUMERIC(*cp)) {
102 103 104 105 106 107 108
			cp++;
			continue;
		}
		if(toupper(*np) != toupper(*cp))
			break;
		np++, cp++;
	}
109 110 111 112
	while(*np != '\0' && !IS_ALPHANUMERIC(*np))
		np++;
	while(*cp != '\0' && !IS_ALPHANUMERIC(*cp) && *cp != '@' )
		cp++;
113 114 115
	return *np == '\0' && (*cp == '\0' || *cp == '@');
}

116
/****************************************************************************/
117
uint total_users(scfg_t* cfg)
118 119 120 121 122 123 124 125 126
{
    char	str[MAX_PATH+1];
    uint	total_users=0;
	int		file;
    long	l,length;

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

127
	if((file=openuserdat(cfg, /* for_modify: */FALSE)) < 0)
128
		return(0);
129
	length=(long)filelength(file);
130 131
	for(l=0;l<length;l+=U_LEN) {
		lseek(file,l+U_MISC,SEEK_SET);
132 133
		if(read(file,str,8)!=8)
			continue;
134 135 136 137 138 139 140 141 142 143
		getrec(str,0,8,str);
		if(ahtoul(str)&(DELETED|INACTIVE))
			continue;
		total_users++;
	}
	close(file);
	return(total_users);
}


144
/****************************************************************************/
145
/* Returns the number of the last user in user.dat (deleted ones too)		*/
146
/****************************************************************************/
147
uint lastuser(scfg_t* cfg)
148 149 150 151
{
	char str[256];
	long length;

152 153 154
	if(!VALID_CFG(cfg))
		return(0);

155
	SAFEPRINTF(str,"%suser/user.dat", cfg->data_dir);
156
	if((length=(long)flength(str))>0)
157 158 159 160
		return((uint)(length/U_LEN));
	return(0);
}

rswindell's avatar
rswindell committed
161
/****************************************************************************/
162
/* Deletes (completely removes) last user record in user.dat				*/
rswindell's avatar
rswindell committed
163
/****************************************************************************/
164
BOOL del_lastuser(scfg_t* cfg)
rswindell's avatar
rswindell committed
165 166 167 168
{
	int		file;
	long	length;

169 170 171
	if(!VALID_CFG(cfg))
		return(FALSE);

172
	if((file=openuserdat(cfg, /* for_modify: */TRUE)) < 0)
rswindell's avatar
rswindell committed
173
		return(FALSE);
174
	length=(long)filelength(file);
rswindell's avatar
rswindell committed
175 176 177 178 179 180 181 182 183
	if(length<U_LEN) {
		close(file);
		return(FALSE);
	}
	chsize(file,length-U_LEN);
	close(file);
	return(TRUE);
}

184
/****************************************************************************/
185
/* Opens the user database returning the file descriptor or -1 on error		*/
186
/****************************************************************************/
187
int openuserdat(scfg_t* cfg, BOOL for_modify)
188
{
189
	char path[MAX_PATH+1];
190

191
	if(!VALID_CFG(cfg))
192
		return(-1);
193

194
	SAFEPRINTF(path,"%suser/user.dat",cfg->data_dir);
195
	return nopen(path, for_modify ? (O_RDWR|O_CREAT|O_DENYNONE) : (O_RDONLY|O_DENYNONE));
196 197
}

198
int closeuserdat(int file)
199
{
200 201
	if(file < 1)
		return -1;
202 203 204
	return close(file);
}

205 206 207 208 209
/****************************************************************************/
/* 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.													*/
/****************************************************************************/
210
int readuserdat(scfg_t* cfg, unsigned user_number, char* userdat, int infile)
211 212
{
	int i,file;
213 214

	if(!VALID_CFG(cfg) || user_number<1)
215
		return(-1);
216

217
	if(infile >= 0)
218 219
		file = infile;
	else {
220
		if((file = openuserdat(cfg, /* for_modify: */FALSE)) < 0)
221 222
			return file;
	}
223 224

	if(user_number > (unsigned)(filelength(file)/U_LEN)) {
225 226
		if(file != infile)
			close(file);
227 228
		return(-1);	/* no such user record */
	}
229

230
	lseek(file,(long)((long)(user_number-1)*U_LEN),SEEK_SET);
231 232
	i=0;
	while(i<LOOP_NODEDAB
233
		&& lock(file,(long)((long)(user_number-1)*U_LEN),U_LEN)==-1) {
234 235
		if(i)
			mswait(100);
236
		i++;
237 238
	}
	if(i>=LOOP_NODEDAB) {
239 240
		if(file != infile)
			close(file);
241
		return(-2);
242 243 244
	}

	if(read(file,userdat,U_LEN)!=U_LEN) {
245
		unlock(file,(long)((long)(user_number-1)*U_LEN),U_LEN);
246 247
		if(file != infile)
			close(file);
248
		return(-3);
249
	}
250
	unlock(file,(long)((long)(user_number-1)*U_LEN),U_LEN);
251 252 253 254 255 256 257 258 259
	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		*/
/****************************************************************************/
260
int parseuserdat(scfg_t* cfg, char *userdat, user_t *user)
261 262 263 264 265 266 267
{
	char str[U_LEN+1];
	int i;
	unsigned user_number;

	if(user==NULL)
		return(-1);
268

269 270 271 272
	user_number=user->number;
	memset(user,0,sizeof(user_t));

	if(!VALID_CFG(cfg) || user_number < 1)
273 274
		return(-1);

275 276 277 278
	/* 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 */

279
	/* order of these function calls is irrelevant */
280 281 282 283 284 285 286 287 288 289 290
	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);
291 292
	if(user->pass[0] == 0)	// Backwards compatibility hack
		getrec(userdat, U_OLDPASS, LEN_OLDPASS, user->pass);
293 294 295
	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
296
	getrec(userdat,U_IPADDR,LEN_IPADDR,user->ipaddr);
297 298 299 300 301 302 303 304
	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 */
305
	getrec(userdat,U_LOGONTIME,8,str); user->logontime=ahtoul(str);
306 307 308 309 310 311 312 313 314 315 316 317

	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);
318
	getrec(userdat,U_ULB,10,str); user->ulb=strtoul(str, NULL, 10);
319
	getrec(userdat,U_ULS,5,str); user->uls=atoi(str);
320
	getrec(userdat,U_DLB,10,str); user->dlb=strtoul(str, NULL, 10);
321
	getrec(userdat,U_DLS,5,str); user->dls=atoi(str);
322 323
	getrec(userdat,U_CDT,10,str); user->cdt=strtoul(str, NULL, 10);
	getrec(userdat,U_MIN,10,str); user->min=strtoul(str, NULL, 10);
324
	getrec(userdat,U_LEVEL,2,str); user->level=atoi(str);
325
	getrec(userdat,U_FLAGS1,8,str); user->flags1=ahtoul(str);
326 327 328 329 330
	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);
331 332 333 334 335 336 337 338 339
	if(getrec(userdat,U_OLDROWS,2,str) <= 0) // Moved to new location
		getrec(userdat, U_ROWS, LEN_ROWS, str);
	user->rows = atoi(str);
	if(user->rows && user->rows < TERM_ROWS_MIN)
		user->rows = TERM_ROWS_MIN;
	getrec(userdat, U_COLS, LEN_COLS, str);
	user->cols = atoi(str);
	if(user->cols && user->cols < TERM_COLS_MIN)
		user->cols = TERM_COLS_MIN;
340 341
	user->sex=userdat[U_SEX];
	if(!user->sex)
342
		user->sex=' ';	 /* fix for v1b04 that could save as 0 */
343
	user->prot=userdat[U_PROT];
344 345
	if(user->prot<' ')
		user->prot=' ';
346 347 348 349 350 351
	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);
352 353
	getrec(userdat,U_CURSUB,sizeof(user->cursub)-1,user->cursub);
	getrec(userdat,U_CURDIR,sizeof(user->curdir)-1,user->curdir);
354
	getrec(userdat,U_CURXTRN,8,user->curxtrn);
355 356

	getrec(userdat,U_FREECDT,10,str);
357
	user->freecdt=strtoul(str, NULL, 10);
358 359 360

	getrec(userdat,U_XEDIT,8,str);
	for(i=0;i<cfg->total_xedits;i++)
361
		if(!stricmp(str,cfg->xedit[i]->code))
362 363 364 365 366 367 368 369 370 371 372 373 374 375
			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);
376
	if(str[0]<' ') { 			   /* v1c, so set defaults */
377
		if(user->rest&FLAG('Q'))
rswindell's avatar
rswindell committed
378
			user->qwk=QWK_DEFAULT|QWK_RETCTLA;
379
		else
380
			user->qwk=QWK_DEFAULT;
381
	}
382 383 384 385 386 387 388 389 390
	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);
391 392 393 394 395 396 397
	/* 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);
398
		if(localtime_r(&now, &now_tm)!=NULL
399
			&& localtime32(&user->logontime, &logon_tm)!=NULL) {
400 401 402 403 404
			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);
		}
405
	}
406 407 408
	return(0);
}

409 410 411
/****************************************************************************/
/* Fills the structure 'user' with info for user.number	from user.dat file	*/
/****************************************************************************/
412
int getuserdat(scfg_t* cfg, user_t *user)
413 414 415 416 417 418
{
	int		retval;
	int		file;
	char	userdat[U_LEN+1];

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

421 422
	if((file = openuserdat(cfg, /* for_modify: */FALSE)) < 0) {
		user->number = 0;
423
		return file;
424
	}
425 426 427 428

	memset(userdat, 0, sizeof(userdat));
	if((retval = readuserdat(cfg, user->number, userdat, file)) != 0) {
		close(file);
429
		user->number = 0;
430 431 432 433 434 435 436
		return retval;
	}
	retval = parseuserdat(cfg, userdat, user);
	close(file);
	return retval;
}

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

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

446
	memset(userdat, 0, sizeof(userdat));
447 448
	if((retval = readuserdat(cfg, user->number, userdat, file)) != 0) {
		user->number = 0;
449
		return retval;
450
	}
451 452 453
	return parseuserdat(cfg, userdat, user);
}

454 455 456 457
/****************************************************************************/
/****************************************************************************/
static void dirtyuserdat(scfg_t* cfg, uint usernumber)
{
458
	int	i,file = -1;
459 460 461 462 463
    node_t	node;

	for(i=1;i<=cfg->sys_nodes;i++) { /* instant user data update */
//		if(i==cfg->node_num)
//			continue;
464
		if(getnodedat(cfg, i,&node, /* lockit: */FALSE, &file) != 0)
465
			continue;
466 467
		if(node.useron==usernumber && (node.status==NODE_INUSE
			|| node.status==NODE_QUIET)) {
468
			if(getnodedat(cfg, i,&node, /* lockit: */TRUE, &file) == 0) {
469
				node.misc|=NODE_UDAT;
470
				putnodedat(cfg, i,&node, /* closeit: */FALSE, file);
471
			}
472 473
			break;
		}
474
	}
475
	CLOSE_OPEN_FILE(file);
476 477
}

478
/****************************************************************************/
479
/****************************************************************************/
480
int is_user_online(scfg_t* cfg, uint usernumber)
481 482
{
	int i;
483
	int file = -1;
484 485 486
	node_t	node;

	for(i=1; i<=cfg->sys_nodes; i++) {
487
		getnodedat(cfg, i, &node, /* lockit: */FALSE, &file);
488 489
		if((node.status==NODE_INUSE || node.status==NODE_QUIET
			|| node.status==NODE_LOGON) && node.useron==usernumber)
490
			return i;
491
	}
492
	CLOSE_OPEN_FILE(file);
493 494 495 496
	return 0;
}

/****************************************************************************/
497
/* Writes into user.number's slot in user.dat data in structure 'user'      */
498 499
/* Called from functions newuser, useredit and main                         */
/****************************************************************************/
500
int putuserdat(scfg_t* cfg, user_t* user)
501
{
502
    int		i,file;
503
    char	userdat[U_LEN],str[MAX_PATH+1];
504

505 506 507 508
	if(user==NULL)
		return(-1);

	if(!VALID_CFG(cfg) || user->number<1)
509
		return(-1);
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532

	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);
533
	putrec(userdat,U_OLDPASS,LEN_OLDPASS,user->pass);	// So a sysop can downgrade to a previous build/version
534 535 536
	putrec(userdat,U_PHONE,LEN_PHONE,user->phone);
	putrec(userdat,U_BIRTH,LEN_BIRTH,user->birth);
	putrec(userdat,U_MODEM,LEN_MODEM,user->modem);
rswindell's avatar
rswindell committed
537
	putrec(userdat,U_IPADDR,LEN_IPADDR,user->ipaddr);
rswindell's avatar
rswindell committed
538 539 540 541
	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));
542 543
	putrec(userdat,U_PWMOD+8,2,crlf);

544 545 546 547 548 549 550 551 552 553 554
	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));
555 556 557
	putrec(userdat,U_PTODAY+5,2,crlf);

	putrec(userdat,U_ULB,10,ultoa(user->ulb,str,10));
558
	putrec(userdat,U_ULS,5,ultoa(user->uls,str,10));
559
	putrec(userdat,U_DLB,10,ultoa(user->dlb,str,10));
560
	putrec(userdat,U_DLS,5,ultoa(user->dls,str,10));
561 562 563 564
	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);

565
	putrec(userdat,U_LEVEL,2,ultoa(user->level,str,10));
566 567 568 569 570 571 572
	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);

573 574
	putrec(userdat, U_ROWS, LEN_ROWS, ultoa(user->rows,str,10));
	putrec(userdat, U_COLS, LEN_COLS, ultoa(user->cols,str,10));
575 576 577
	userdat[U_SEX]=user->sex;
	userdat[U_PROT]=user->prot;
	putrec(userdat,U_MISC,8,ultoa(user->misc,str,16));
578
	putrec(userdat,U_LEECH,2,ultoa(user->leech,str,16));
579

580 581
	putrec(userdat,U_CURSUB,sizeof(user->cursub)-1,user->cursub);
	putrec(userdat,U_CURDIR,sizeof(user->curdir)-1,user->curdir);
582 583
	putrec(userdat,U_CURXTRN,8,user->curxtrn);
	putrec(userdat,U_CURXTRN+8,2,crlf);
584

585
	putrec(userdat,U_PASS+LEN_PASS, 2, crlf);
586

deuce's avatar
deuce committed
587
	putrec(userdat,U_IPADDR+LEN_IPADDR,2,crlf);
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603

	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
604 605
	putrec(userdat,U_NS_TIME,8,ultoa((ulong)user->ns_time,str,16));
	putrec(userdat,U_LOGONTIME,8,ultoa((ulong)user->logontime,str,16));
606

607 608
	putrec(userdat,U_UNUSED,U_LEN-(U_UNUSED)-2,crlf);
	putrec(userdat,U_UNUSED+(U_LEN-(U_UNUSED)-2),2,crlf);
609

610
	if((file=openuserdat(cfg, /* for_modify: */TRUE)) < 0)
611 612
		return(errno);

613 614 615 616 617
	if(filelength(file)<((long)user->number-1)*U_LEN) {
		close(file);
		return(-4);
	}

618 619 620 621 622
	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) {
623 624
		if(i)
			mswait(100);
625
		i++;
626 627 628 629
	}

	if(i>=LOOP_NODEDAB) {
		close(file);
630
		return(-2);
631 632 633 634 635
	}

	if(write(file,userdat,U_LEN)!=U_LEN) {
		unlock(file,(long)((long)(user->number-1)*U_LEN),U_LEN);
		close(file);
636
		return(-3);
637 638 639
	}
	unlock(file,(long)((long)(user->number-1)*U_LEN),U_LEN);
	close(file);
640
	dirtyuserdat(cfg,user->number);
641 642 643 644 645 646 647 648
	return(0);
}


/****************************************************************************/
/* Returns the username in 'str' that corresponds to the 'usernumber'       */
/* Called from functions everywhere                                         */
/****************************************************************************/
649
char* username(scfg_t* cfg, int usernumber, char *name)
650
{
651 652 653
    char	str[256];
    int		c;
    int		file;
654

655
	if(name==NULL)
656 657 658
		return(NULL);

	if(!VALID_CFG(cfg) || usernumber<1) {
659
		name[0]=0;
660
		return(name);
661
	}
662
	SAFEPRINTF(str,"%suser/name.dat",cfg->data_dir);
663
	if(flength(str)<1L) {
664
		name[0]=0;
665
		return(name);
666
	}
667
	if((file=nopen(str,O_RDONLY))==-1) {
668
		name[0]=0;
669
		return(name);
670
	}
671 672
	if(filelength(file)<(long)((long)usernumber*(LEN_ALIAS+2))) {
		close(file);
673
		name[0]=0;
674
		return(name);
675
	}
676
	lseek(file,(long)((long)(usernumber-1)*(LEN_ALIAS+2)),SEEK_SET);
677
	read(file,name,LEN_ALIAS);
678 679
	close(file);
	for(c=0;c<LEN_ALIAS;c++)
680 681
		if(name[c]==ETX) break;
	name[c]=0;
682
	if(!c)
683 684
		strcpy(name,"DELETED USER");
	return(name);
685 686
}

687 688 689
/****************************************************************************/
/* Puts 'name' into slot 'number' in user/name.dat							*/
/****************************************************************************/
690
int putusername(scfg_t* cfg, int number, const char *name)
691 692 693
{
	char str[256];
	int file;
rswindell's avatar
rswindell committed
694
	int wr;
695
	long length;
696
	uint total_users;
697

698
	if(!VALID_CFG(cfg) || name==NULL || number<1)
699 700
		return(-1);

701
	SAFEPRINTF(str,"%suser/name.dat", cfg->data_dir);
702 703
	if((file=nopen(str,O_RDWR|O_CREAT))==-1)
		return(errno);
704
	length=(long)filelength(file);
705 706 707 708 709 710

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

711 712
	if(length && length%(LEN_ALIAS+2)) {
		close(file);
713
		return(-3);
714 715
	}
	if(length<(((long)number-1)*(LEN_ALIAS+2))) {
716
		SAFEPRINTF2(str,"%*s\r\n",LEN_ALIAS,nulstr);
717 718 719
		memset(str,ETX,LEN_ALIAS);
		lseek(file,0L,SEEK_END);
		while(filelength(file)<((long)number*(LEN_ALIAS+2)))
720
			write(file,str,(LEN_ALIAS+2));
721 722 723 724
	}
	lseek(file,(long)(((long)number-1)*(LEN_ALIAS+2)),SEEK_SET);
	putrec(str,0,LEN_ALIAS,name);
	putrec(str,LEN_ALIAS,2,crlf);
rswindell's avatar
rswindell committed
725
	wr=write(file,str,LEN_ALIAS+2);
726 727
	close(file);

rswindell's avatar
rswindell committed
728 729
	if(wr!=LEN_ALIAS+2)
		return(errno);
730 731 732
	return(0);
}

733 734
#define DECVAL(ch, mul)	(DEC_CHAR_TO_INT(ch) * (mul))

735
int getbirthyear(const char* birth)
736
{
737
	if(IS_DIGIT(birth[2]))				// CCYYMMYY format
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
		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;
}

754
int getbirthmonth(scfg_t* cfg, const char* birth)
755
{
756
	if(IS_DIGIT(birth[5]))				// CCYYMMYY format
757 758 759 760 761 762 763 764
		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);
	}
}

765
int getbirthday(scfg_t* cfg, const char* birth)
766
{
767
	if(IS_DIGIT(birth[5]))				// CCYYMMYY format
768 769 770 771 772 773 774 775
		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);
	}
}

776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804
// Always returns string in MM/DD/YY format
char* getbirthmmddyy(scfg_t* cfg, const char* birth, char* buf, size_t max)
{
	safe_snprintf(buf, max, "%02u/%02u/%02u"
		, getbirthmonth(cfg, birth)
		, getbirthday(cfg, birth)
		, getbirthyear(birth) % 100);
	return buf;
}

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

char* getbirthdstr(scfg_t* cfg, const char* birth, char* buf, size_t max)
{
	if(cfg->sys_misc & SM_EURODATE)
		getbirthddmmyy(cfg, birth, buf, max);
	else
		getbirthmmddyy(cfg, birth, buf, max);
	return buf;
}

805
/****************************************************************************/
806 807
/* Returns the age derived from the string 'birth' in the format CCYYMMDD	*/
/* or legacy: MM/DD/YY or DD/MM/YY											*/
808
/****************************************************************************/
809
int getage(scfg_t* cfg, const char *birth)
810
{
811
	struct	tm tm;
812 813
	time_t	now;

814 815 816
	if(!VALID_CFG(cfg) || birth==NULL)
		return(0);

817 818 819 820
	if(!atoi(birth) || !atoi(birth+3))	/* Invalid */
		return(0);

	now=time(NULL);
821
	if(localtime_r(&now,&tm)==NULL)
822
		return(0);
823

824
	tm.tm_mon++;	/* convert to 1 based */
825 826 827 828 829 830
	int year = getbirthyear(birth);
	int age = (1900 + tm.tm_year) - year;
	int mon = getbirthmonth(cfg, birth);
	if(mon > tm.tm_mon || (mon == tm.tm_mon && getbirthday(cfg, birth) > tm.tm_mday))
		age--;
	return age;
831 832
}

833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868
/****************************************************************************/
/* Converts from either MM/DD/YYYYY or DD/MM/YYYY to YYYYMMDD				*/
/****************************************************************************/
char* parse_birthdate(scfg_t* cfg, const char* birthdate, char* out, size_t maxlen)
{
	if(cfg->sys_misc & SM_EURODATE)
		safe_snprintf(out, maxlen, "%.4s%.2s%.2s", birthdate + 6, birthdate + 3, birthdate);
	else
		safe_snprintf(out, maxlen, "%.4s%.2s%.2s", birthdate + 6, birthdate, birthdate + 3);
	return out;
}

/****************************************************************************/
/* Converts from user birth date to either MM/DD/YYYYY or DD/MM/YYYY		*/
/****************************************************************************/
char* format_birthdate(scfg_t* cfg, const char* birthdate, char* out, size_t maxlen)
{
	if(maxlen < 1)
		return NULL;
	*out = '\0';
	if(*birthdate) {
		if(cfg->sys_misc & SM_EURODATE)
			safe_snprintf(out, maxlen, "%02u/%02u/%04u"
				,getbirthday(cfg, birthdate), getbirthmonth(cfg, birthdate), getbirthyear(birthdate));
		else
			safe_snprintf(out, maxlen, "%02u/%02u/%04u"
				,getbirthmonth(cfg, birthdate), getbirthday(cfg, birthdate), getbirthyear(birthdate));
	}
	return out;
}

const char* birthdate_format(scfg_t* cfg)
{
	return cfg->sys_misc&SM_EURODATE ? "DD/MM/YYYY" : "MM/DD/YYYY";
}

869 870 871 872 873 874 875 876 877 878 879 880 881
/****************************************************************************/
/****************************************************************************/
int opennodedat(scfg_t* cfg)
{
	char	fname[MAX_PATH+1];

	if(!VALID_CFG(cfg))
		return -1;

	SAFEPRINTF(fname, "%snode.dab", cfg->ctrl_dir);
	return nopen(fname, O_RDWR|O_DENYNONE);
}

882 883 884 885 886 887 888 889 890 891 892 893 894
/****************************************************************************/
/****************************************************************************/
int opennodeext(scfg_t* cfg)
{
	char	fname[MAX_PATH+1];

	if(!VALID_CFG(cfg))
		return -1;

	SAFEPRINTF(fname, "%snode.exb", cfg->ctrl_dir);
	return nopen(fname, O_RDWR|O_DENYNONE);
}

895 896
/****************************************************************************/
/* Reads the data for node number 'number' into the structure 'node'        */