Skip to content
Snippets Groups Projects
js_user.c 51.5 KiB
Newer Older
/* Synchronet JavaScript "User" Object */

/****************************************************************************
 * @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 "sbbs.h"
	user_t* user;
	user_t storage;
	JSBool cached;
	client_t* client;
	int file;               // for fast read operations, only
/* User Object Properties */
	USER_PROP_NUMBER
	, USER_PROP_ALIAS
	, USER_PROP_NAME
	, USER_PROP_HANDLE
	, USER_PROP_LANG
	, USER_PROP_NOTE
	, USER_PROP_IPADDR
	, USER_PROP_COMP
	, USER_PROP_COMMENT
	, USER_PROP_NETMAIL
	, USER_PROP_EMAIL    /* READ ONLY */
	, USER_PROP_ADDRESS
	, USER_PROP_LOCATION
	, USER_PROP_ZIPCODE
	, USER_PROP_PASS
	, USER_PROP_PHONE
	, USER_PROP_BIRTH
	, USER_PROP_BIRTHYEAR
	, USER_PROP_BIRTHMONTH
	, USER_PROP_BIRTHDAY
	, USER_PROP_AGE      /* READ ONLY */
	, USER_PROP_MODEM
	, USER_PROP_LASTON
	, USER_PROP_FIRSTON
	, USER_PROP_EXPIRE
	, USER_PROP_PWMOD
	, USER_PROP_DELDATE
	, USER_PROP_LOGONS
	, USER_PROP_LTODAY
	, USER_PROP_TIMEON
	, USER_PROP_TEXTRA
	, USER_PROP_TTODAY
	, USER_PROP_TLAST
	, USER_PROP_POSTS
	, USER_PROP_EMAILS
	, USER_PROP_FBACKS
	, USER_PROP_ETODAY
	, USER_PROP_PTODAY
	, USER_PROP_MAIL_WAITING
	, USER_PROP_READ_WAITING
	, USER_PROP_UNREAD_WAITING
	, USER_PROP_SPAM_WAITING
	, USER_PROP_MAIL_PENDING
	, USER_PROP_ULB
	, USER_PROP_ULS
	, USER_PROP_DLB
	, USER_PROP_DLS
	, USER_PROP_DLCPS
	, USER_PROP_CDT
	, USER_PROP_MIN
	, USER_PROP_LEVEL
	, USER_PROP_FLAGS1
	, USER_PROP_FLAGS2
	, USER_PROP_FLAGS3
	, USER_PROP_FLAGS4
	, USER_PROP_EXEMPT
	, USER_PROP_REST
	, USER_PROP_ROWS
	, USER_PROP_COLS
	, USER_PROP_SEX
	, USER_PROP_MISC
	, USER_PROP_LEECH
	, USER_PROP_CURSUB
	, USER_PROP_CURDIR
	, USER_PROP_CURXTRN
	, USER_PROP_FREECDT
	, USER_PROP_XEDIT
	, USER_PROP_SHELL
	, USER_PROP_QWK
	, USER_PROP_TMPEXT
	, USER_PROP_CHAT
	, USER_PROP_MAIL
	, USER_PROP_NS_TIME
	, USER_PROP_PROT
	, USER_PROP_LOGONTIME
	, USER_PROP_TIMEPERCALL
	, USER_PROP_TIMEPERDAY
	, USER_PROP_CALLSPERDAY
	, USER_PROP_LINESPERMSG
	, USER_PROP_EMAILPERDAY
	, USER_PROP_POSTSPERDAY
	, USER_PROP_FREECDTPERDAY
	, USER_PROP_CACHED
	, USER_PROP_IS_SYSOP
	, USER_PROP_BATUPLST
	, USER_PROP_BATDNLST
static void js_getuserdat(scfg_t* scfg, private_t* p)
	if (p->user->number != 0 && !p->cached) {
		if (p->file < 1)
			p->file = openuserdat(scfg, /* for_modify: */ FALSE);
		ushort usernumber = p->user->number;
		if (fgetuserdat(scfg, p->user, p->file) == 0)
			p->cached = TRUE;
		p->user->number = usernumber; // Can be zeroed by fgetuserdat() failure
static JSBool js_user_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
	jsval      idval;
	char*      s = NULL;
	char       tmp[128];
	int64_t    val = 0;
	jsint      tiny;
	JSString*  js_str;
	private_t* p;
	jsrefcount rc;
	scfg_t*    scfg;

	scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx));

	if ((p = (private_t*)JS_GetPrivate(cx, obj)) == NULL)
	rc = JS_SUSPENDREQUEST(cx);
	js_getuserdat(scfg, p);
	JS_RESUMEREQUEST(cx, rc);
	JS_IdToValue(cx, id, &idval);
	tiny = JSVAL_TO_INT(idval);
	rc = JS_SUSPENDREQUEST(cx);
rswindell's avatar
rswindell committed
		case USER_PROP_NUMBER:
			val = p->user->number;
rswindell's avatar
rswindell committed
			break;
			s = p->user->alias;
			break;
		case USER_PROP_HANDLE:
			s = p->user->handle;
deuce's avatar
deuce committed
		case USER_PROP_IPADDR:
			s = p->user->ipaddr;
deuce's avatar
deuce committed
			break;
			break;
		case USER_PROP_COMMENT:
			s = p->user->comment;
			break;
		case USER_PROP_NETMAIL:
			s = p->user->netmail;
			s = usermailaddr(scfg, tmp
			                 , (scfg->inetmail_misc & NMAIL_ALIAS) || (p->user->rest & FLAG('O')) ? p->user->alias : p->user->name);
			s = p->user->address;
			break;
		case USER_PROP_LOCATION:
			s = p->user->location;
			break;
		case USER_PROP_ZIPCODE:
			s = p->user->zipcode;
			s = p->user->phone;
			s = p->user->birth;
		case USER_PROP_BIRTHYEAR:
			val = getbirthyear(scfg, p->user->birth);
			break;
		case USER_PROP_BIRTHMONTH:
			val = getbirthmonth(scfg, p->user->birth);
			break;
		case USER_PROP_BIRTHDAY:
			val = getbirthday(scfg, p->user->birth);
			break;
			val = getage(scfg, p->user->birth);
			s = p->user->connection;
			break;
		case USER_PROP_LASTON:
			val = p->user->laston;
			break;
		case USER_PROP_FIRSTON:
			val = p->user->firston;
			break;
		case USER_PROP_EXPIRE:
			val = p->user->expire;
			val = p->user->pwmod;
		case USER_PROP_DELDATE:
			val = p->user->deldate;
			break;
			val = p->user->logons;
			break;
		case USER_PROP_LTODAY:
			val = p->user->ltoday;
			break;
		case USER_PROP_TIMEON:
			val = p->user->timeon;
			break;
		case USER_PROP_TEXTRA:
			val = p->user->textra;
			break;
		case USER_PROP_TTODAY:
			val = p->user->ttoday;
			val = p->user->tlast;
			val = p->user->posts;
			val = p->user->emails;
			val = p->user->fbacks;
			val = p->user->etoday;
			break;
		case USER_PROP_PTODAY:
			val = p->user->ptoday;
			*vp = DOUBLE_TO_JSVAL((jsdouble)p->user->ulb);
			JS_RESUMEREQUEST(cx, rc);
			return JS_TRUE; /* intentional early return */
			val = p->user->uls;
			*vp = DOUBLE_TO_JSVAL((jsdouble)p->user->dlb);
			JS_RESUMEREQUEST(cx, rc);
			return JS_TRUE; /* intentional early return */
			val = p->user->dls;
		case USER_PROP_DLCPS:
			val = p->user->dlcps;
			*vp = DOUBLE_TO_JSVAL((jsdouble)p->user->cdt);
			JS_RESUMEREQUEST(cx, rc);
			return JS_TRUE; /* intentional early return */
			val = p->user->min;
			val = p->user->level;
			break;
		case USER_PROP_FLAGS1:
			val = p->user->flags1;
			break;
		case USER_PROP_FLAGS2:
			val = p->user->flags2;
			break;
		case USER_PROP_FLAGS3:
			val = p->user->flags3;
			break;
		case USER_PROP_FLAGS4:
			val = p->user->flags4;
			break;
		case USER_PROP_EXEMPT:
			val = p->user->exempt;
			val = p->user->rest;
			val = p->user->rows;
		case USER_PROP_COLS:
			val = p->user->cols;
			sprintf(tmp, "%c", p->user->sex);
			s = tmp;
		case USER_PROP_MISC:
			val = p->user->misc;
			val = p->user->leech;
			break;
		case USER_PROP_CURSUB:
			s = p->user->cursub;
			break;
		case USER_PROP_CURDIR:
			s = p->user->curdir;
			s = p->user->curxtrn;
			*vp = DOUBLE_TO_JSVAL((jsdouble)p->user->freecdt);
			JS_RESUMEREQUEST(cx, rc);
			return JS_TRUE; /* intentional early return */
			if (p->user->xedit > 0 && p->user->xedit <= scfg->total_xedits)
				s = scfg->xedit[p->user->xedit - 1]->code;
				s = ""; /* internal editor */
			s = scfg->shell[p->user->shell]->code;
			val = p->user->qwk;
			break;
		case USER_PROP_TMPEXT:
			s = p->user->tmpext;
			val = p->user->chat;
		case USER_PROP_MAIL:
			val = p->user->mail;
			break;
			val = p->user->ns_time;
			sprintf(tmp, "%c", p->user->prot);
			s = tmp;
			val = p->user->logontime;
			val = scfg->level_timepercall[p->user->level];
			val = scfg->level_timeperday[p->user->level];
			val = scfg->level_callsperday[p->user->level];
			val = scfg->level_linespermsg[p->user->level];
			val = scfg->level_postsperday[p->user->level];
			val = scfg->level_emailperday[p->user->level];
			val = scfg->level_freecdtperday[p->user->level];
			val = getmail(scfg, p->user->number, /* sent? */ FALSE, /* attr: */ 0);
			break;
		case USER_PROP_READ_WAITING:
			val = getmail(scfg, p->user->number, /* sent? */ FALSE, /* attr: */ MSG_READ);
			break;
		case USER_PROP_UNREAD_WAITING:
			val = getmail(scfg, p->user->number, /* sent? */ FALSE, /* attr: */ ~MSG_READ);
			break;
		case USER_PROP_SPAM_WAITING:
			val = getmail(scfg, p->user->number, /* sent? */ FALSE, /* attr: */ MSG_SPAM);
			val = getmail(scfg, p->user->number, /* sent? */ TRUE, /* SPAM: */ FALSE);
		case USER_PROP_CACHED:
			*vp = BOOLEAN_TO_JSVAL(p->cached);
			return JS_TRUE;    /* intentional early return */
			*vp = BOOLEAN_TO_JSVAL(user_is_sysop(p->user));
			return JS_TRUE;    /* intentional early return */
		case USER_PROP_BATUPLST:
			s = batch_list_name(scfg, p->user->number, XFER_BATCH_UPLOAD, tmp, sizeof tmp);
			break;
		case USER_PROP_BATDNLST:
			s = batch_list_name(scfg, p->user->number, XFER_BATCH_DOWNLOAD, tmp, sizeof tmp);
			break;

			/* This must not set vp in order for child objects to work (stats and security) */
	if (s != NULL) {
		if ((js_str = JS_NewStringCopyZ(cx, s)) == NULL)
		*vp = STRING_TO_JSVAL(js_str);
		*vp = DOUBLE_TO_JSVAL((double)val);
static JSBool js_user_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
	jsval      idval;
	char*      str = NULL;
	uint32     val;
	ulong      usermisc;
	jsint      tiny;
	private_t* p;
	int32      usernumber;
	jsrefcount rc;
	scfg_t*    scfg;

	scfg = JS_GetRuntimePrivate(JS_GetRuntime(cx));

	if ((p = (private_t*)JS_GetPrivate(cx, obj)) == NULL)
deuce's avatar
deuce committed
	JSVALUE_TO_MSTRING(cx, *vp, str, NULL);
	HANDLE_PENDING(cx, str);
	JS_IdToValue(cx, id, &idval);
	tiny = JSVAL_TO_INT(idval);
	rc = JS_SUSPENDREQUEST(cx);
	switch (tiny) {
rswindell's avatar
rswindell committed
		case USER_PROP_NUMBER:
			if (!JS_ValueToInt32(cx, *vp, &usernumber)) {
deuce's avatar
deuce committed
				free(str);
			rc = JS_SUSPENDREQUEST(cx);
			if (usernumber != p->user->number) {
				p->user->number = (ushort)usernumber;
				p->cached = FALSE;
rswindell's avatar
rswindell committed
			break;
			SAFECOPY(p->user->alias, str);
			putuserstr(scfg, p->user->number, USER_ALIAS, str);
			usermisc = getusermisc(scfg, p->user->number);
			if (!(usermisc & DELETED))
				putusername(scfg, p->user->number, str);
			SAFECOPY(p->user->name, str);
			putuserstr(scfg, p->user->number, USER_NAME, str);
			break;
		case USER_PROP_HANDLE:
			SAFECOPY(p->user->handle, str);
			putuserstr(scfg, p->user->number, USER_HANDLE, str);
		case USER_PROP_LANG:
			SAFECOPY(p->user->lang, str);
			putuserstr(scfg, p->user->number, USER_LANG, str);
			break;
			SAFECOPY(p->user->note, str);
			putuserstr(scfg, p->user->number, USER_NOTE, str);
			SAFECOPY(p->user->ipaddr, str);
			putuserstr(scfg, p->user->number, USER_IPADDR, str);
deuce's avatar
deuce committed
			break;
			SAFECOPY(p->user->comp, str);
			putuserstr(scfg, p->user->number, USER_HOST, str);
			SAFECOPY(p->user->comment, str);
			putuserstr(scfg, p->user->number, USER_COMMENT, str);
			SAFECOPY(p->user->netmail, str);
			putuserstr(scfg, p->user->number, USER_NETMAIL, str);
			SAFECOPY(p->user->address, str);
			putuserstr(scfg, p->user->number, USER_ADDRESS, str);
			SAFECOPY(p->user->location, str);
			putuserstr(scfg, p->user->number, USER_LOCATION, str);
			SAFECOPY(p->user->zipcode, str);
			putuserstr(scfg, p->user->number, USER_ZIPCODE, str);
			SAFECOPY(p->user->phone, str);
			putuserstr(scfg, p->user->number, USER_PHONE, str);
			parse_birthdate(scfg, str, p->user->birth, sizeof p->user->birth);
			putuserstr(scfg, p->user->number, USER_BIRTH, p->user->birth);
		case USER_PROP_BIRTHYEAR:
			if (JS_ValueToECMAUint32(cx, *vp, &val))
				putuserdec32(scfg, p->user->number, USER_BIRTH, isoDate_create(val, getbirthmonth(scfg, p->user->birth), getbirthday(scfg, p->user->birth)));
			break;
		case USER_PROP_BIRTHMONTH:
			if (JS_ValueToECMAUint32(cx, *vp, &val))
				putuserdec32(scfg, p->user->number, USER_BIRTH, isoDate_create(getbirthyear(scfg, p->user->birth), val, getbirthday(scfg, p->user->birth)));
			break;
		case USER_PROP_BIRTHDAY:
			if (JS_ValueToECMAUint32(cx, *vp, &val))
				putuserdec32(scfg, p->user->number, USER_BIRTH, isoDate_create(getbirthyear(scfg, p->user->birth), getbirthmonth(scfg, p->user->birth), val));
			SAFECOPY(p->user->connection, str);
			putuserstr(scfg, p->user->number, USER_CONNECTION, str);
			p->user->rows = atoi(str);
			putuserdec32(scfg, p->user->number, USER_ROWS, p->user->rows);
			p->user->cols = atoi(str);
			putuserdec32(scfg, p->user->number, USER_COLS, p->user->cols);
			p->user->sex = toupper(str[0]);
			putuserstr(scfg, p->user->number, USER_GENDER, strupr(str));    /* single char */
			SAFECOPY(p->user->cursub, str);
			putuserstr(scfg, p->user->number, USER_CURSUB, str);
			SAFECOPY(p->user->curdir, str);
			putuserstr(scfg, p->user->number, USER_CURDIR, str);
			SAFECOPY(p->user->curxtrn, str);
			putuserstr(scfg, p->user->number, USER_CURXTRN, str);
			p->user->xedit = getxeditnum(scfg, str);
			putuserstr(scfg, p->user->number, USER_XEDIT, str);
			p->user->shell = getshellnum(scfg, str, 0);
			putuserstr(scfg, p->user->number, USER_SHELL, str);
			if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
				free(str);
			putusermisc(scfg, p->user->number, p->user->misc = val);
			rc = JS_SUSPENDREQUEST(cx);
			if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
				free(str);
			putuserqwk(scfg, p->user->number, p->user->qwk = val);
			rc = JS_SUSPENDREQUEST(cx);
			if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
				free(str);
			putuserchat(scfg, p->user->number, p->user->chat = val);
			rc = JS_SUSPENDREQUEST(cx);
		case USER_PROP_MAIL:
			JS_RESUMEREQUEST(cx, rc);
			if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
				free(str);
				return JS_FALSE;
			}
			putusermail(scfg, p->user->number, p->user->mail = val);
			rc = JS_SUSPENDREQUEST(cx);
			SAFECOPY(p->user->tmpext, str);
			putuserstr(scfg, p->user->number, USER_TMPEXT, str);
			if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
				free(str);
			putuserdatetime(scfg, p->user->number, USER_NS_TIME, p->user->ns_time = val);
			rc = JS_SUSPENDREQUEST(cx);
			p->user->prot = toupper(str[0]);
			putuserstr(scfg, p->user->number, USER_PROT, strupr(str)); /* single char */
		case USER_PROP_LOGONTIME:
			if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
				free(str);
			putuserdatetime(scfg, p->user->number, USER_LOGONTIME, p->user->logontime = val);
			rc = JS_SUSPENDREQUEST(cx);
			SAFECOPY(p->user->pass, str);
			putuserstr(scfg, p->user->number, USER_PASS, strupr(str));
			if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
				free(str);
			putuserdatetime(scfg, p->user->number, USER_PWMOD, p->user->pwmod = val);
			rc = JS_SUSPENDREQUEST(cx);
			p->user->level = atoi(str);
			putuserdec32(scfg, p->user->number, USER_LEVEL, p->user->level);
			break;
		case USER_PROP_FLAGS1:
			if (JSVAL_IS_STRING(*vp)) {
				val = str_to_bits(p->user->flags1 << 1, str) >> 1;
				if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
					free(str);
			putuserflags(scfg, p->user->number, USER_FLAGS1, p->user->flags1 = val);
			rc = JS_SUSPENDREQUEST(cx);
			break;
		case USER_PROP_FLAGS2:
			if (JSVAL_IS_STRING(*vp)) {
				val = str_to_bits(p->user->flags2 << 1, str) >> 1;
				if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
					free(str);
			putuserflags(scfg, p->user->number, USER_FLAGS2, p->user->flags2 = val);
			rc = JS_SUSPENDREQUEST(cx);
			break;
		case USER_PROP_FLAGS3:
			if (JSVAL_IS_STRING(*vp)) {
				val = str_to_bits(p->user->flags3 << 1, str) >> 1;
				if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
					free(str);
			putuserflags(scfg, p->user->number, USER_FLAGS3, p->user->flags3 = val);
			rc = JS_SUSPENDREQUEST(cx);
			break;
		case USER_PROP_FLAGS4:
			if (JSVAL_IS_STRING(*vp)) {
				val = str_to_bits(p->user->flags4 << 1, str) >> 1;
				if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
					free(str);
			putuserflags(scfg, p->user->number, USER_FLAGS4, p->user->flags4 = val);
			rc = JS_SUSPENDREQUEST(cx);
			break;
		case USER_PROP_EXEMPT:
			if (JSVAL_IS_STRING(*vp)) {
				val = str_to_bits(p->user->exempt << 1, str) >> 1;
				if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
					free(str);
			putuserflags(scfg, p->user->number, USER_EXEMPT, p->user->exempt = val);
			rc = JS_SUSPENDREQUEST(cx);
			if (JSVAL_IS_STRING(*vp)) {
				val = str_to_bits(p->user->rest << 1, str) >> 1;
				if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
					free(str);
			putuserflags(scfg, p->user->number, USER_REST, p->user->rest = val);
			rc = JS_SUSPENDREQUEST(cx);
			p->user->cdt = strtoull(str, NULL, 0);
			putuserdec64(scfg, p->user->number, USER_CDT, p->user->cdt);
			break;
		case USER_PROP_FREECDT:
			p->user->freecdt = strtoull(str, NULL, 0);
			putuserdec64(scfg, p->user->number, USER_FREECDT, p->user->freecdt);
			p->user->min = strtoul(str, NULL, 0);
			putuserdec32(scfg, p->user->number, USER_MIN, p->user->min);
			p->user->textra = (ushort)strtoul(str, NULL, 0);
			putuserdec32(scfg, p->user->number, USER_TEXTRA, p->user->textra);
			if (!JS_ValueToECMAUint32(cx, *vp, &val)) {
deuce's avatar
deuce committed
				free(str);
			putuserdatetime(scfg, p->user->number, USER_EXPIRE, p->user->expire = val);
			rc = JS_SUSPENDREQUEST(cx);

		case USER_PROP_CACHED:
			JS_ValueToBoolean(cx, *vp, &p->cached);
deuce's avatar
deuce committed
			free(str);
			return JS_TRUE;    /* intentional early return */
deuce's avatar
deuce committed
	free(str);
	if (!(p->user->rest & FLAG('G')))
		p->cached = FALSE;
#define USER_PROP_FLAGS JSPROP_ENUMERATE
static jsSyncPropertySpec js_user_properties[] = {
/*		 name				,tinyid					,flags,					ver	*/

	{   "number", USER_PROP_NUMBER, USER_PROP_FLAGS,       310},
	{   "alias", USER_PROP_ALIAS, USER_PROP_FLAGS,       310},
	{   "name", USER_PROP_NAME, USER_PROP_FLAGS,       310},
	{   "handle", USER_PROP_HANDLE, USER_PROP_FLAGS,       310},
	{   "lang", USER_PROP_LANG, USER_PROP_FLAGS,       320},
	{   "note", USER_PROP_NOTE, USER_PROP_FLAGS,       310},
	{   "ip_address", USER_PROP_IPADDR, USER_PROP_FLAGS,       310},
	{   "host_name", USER_PROP_COMP, USER_PROP_FLAGS,       310},
	{   "computer", USER_PROP_COMP, 0, /* Alias */ 310},
	{   "comment", USER_PROP_COMMENT, USER_PROP_FLAGS,       310},
	{   "netmail", USER_PROP_NETMAIL, USER_PROP_FLAGS,       310},
	{   "email", USER_PROP_EMAIL, USER_PROP_FLAGS | JSPROP_READONLY,       310},
	{   "address", USER_PROP_ADDRESS, USER_PROP_FLAGS,       310},
	{   "location", USER_PROP_LOCATION, USER_PROP_FLAGS,       310},
	{   "zipcode", USER_PROP_ZIPCODE, USER_PROP_FLAGS,       310},
	{   "phone", USER_PROP_PHONE, USER_PROP_FLAGS,       310},
	{   "birthdate", USER_PROP_BIRTH, USER_PROP_FLAGS,       310},
	{   "birthyear", USER_PROP_BIRTHYEAR, USER_PROP_FLAGS,       31802},
	{   "birthmonth", USER_PROP_BIRTHMONTH, USER_PROP_FLAGS,       31802},
	{   "birthday", USER_PROP_BIRTHDAY, USER_PROP_FLAGS,       31802},
	{   "age", USER_PROP_AGE, USER_PROP_FLAGS | JSPROP_READONLY,       310},
	{   "connection", USER_PROP_MODEM, USER_PROP_FLAGS,       310},
	{   "modem", USER_PROP_MODEM, 0, /* Alias */ 310},
	{   "screen_rows", USER_PROP_ROWS, USER_PROP_FLAGS,       310},
	{   "screen_columns", USER_PROP_COLS, USER_PROP_FLAGS,       31802},
	{   "gender", USER_PROP_SEX, USER_PROP_FLAGS,       310},
	{   "cursub", USER_PROP_CURSUB, USER_PROP_FLAGS,       310},
	{   "curdir", USER_PROP_CURDIR, USER_PROP_FLAGS,       310},
	{   "curxtrn", USER_PROP_CURXTRN, USER_PROP_FLAGS,       310},
	{   "editor", USER_PROP_XEDIT, USER_PROP_FLAGS,       310},
	{   "command_shell", USER_PROP_SHELL, USER_PROP_FLAGS,       310},
	{   "settings", USER_PROP_MISC, USER_PROP_FLAGS,       310},
	{   "qwk_settings", USER_PROP_QWK, USER_PROP_FLAGS,       310},
	{   "chat_settings", USER_PROP_CHAT, USER_PROP_FLAGS,       310},
	{   "mail_settings", USER_PROP_MAIL, USER_PROP_FLAGS,       320},
	{   "temp_file_ext", USER_PROP_TMPEXT, USER_PROP_FLAGS,       310},
	{   "new_file_time", USER_PROP_NS_TIME, USER_PROP_FLAGS,       311},
	{   "newscan_date", USER_PROP_NS_TIME, 0, /* Alias */ 310},
	{   "download_protocol", USER_PROP_PROT, USER_PROP_FLAGS,       310},
	{   "logontime", USER_PROP_LOGONTIME, USER_PROP_FLAGS,       310},
	{   "cached", USER_PROP_CACHED, USER_PROP_FLAGS,       314},
	{   "is_sysop", USER_PROP_IS_SYSOP, JSPROP_ENUMERATE | JSPROP_READONLY,  315},
	{   "batch_upload_list", USER_PROP_BATUPLST, JSPROP_ENUMERATE | JSPROP_READONLY,  320},
	{   "batch_download_list", USER_PROP_BATDNLST, JSPROP_ENUMERATE | JSPROP_READONLY,  320},
static const char*        user_prop_desc[] = {

	"Record number (1-based)"
	, "Alias/name"
	, "Real name"
	, "Chat handle"
	, "Language code (blank, if default, e.g. English)"
	, "Sysop note"
	, "IP address last logged-in from"
	, "Host name last logged-in from (AKA <tt>computer</tt>)"
	, "Sysop's comment"
	, "External e-mail address"
	, "Local Internet e-mail address	- <small>READ ONLY</small>"
	, "Street address"
	, "Location (e.g. city, state)"
	, "Zip/postal code"
	, "Phone number"
	, "Birth date in 'YYYYMMDD' format or legacy format: 'MM/DD/YY' or 'DD/MM/YY', depending on system configuration"
	, "Birth year"
	, "Birth month (1-12)"
	, "Birth day of month (1-31)"
	, "Calculated age in years - <small>READ ONLY</small>"
	, "Connection type (protocol, AKA <tt>modem</tt>)"
	, "Terminal rows (0 = auto-detect)"
	, "Terminal columns (0 = auto-detect)"
	, "Gender type (e.g. M or F or any single-character)"
	, "Current/last message sub-board (internal code)"
	, "Current/last file directory (internal code)"
	, "Current/last external program (internal code) run"
	, "External message editor (internal code) or <i>blank</i> if none"
	, "Command shell (internal code)"
	, "Settings bit-flags - see <tt>USER_*</tt> in <tt>sbbsdefs.js</tt> for bit definitions"
	, "QWK packet settings bit-flags - see <tt>QWK_*</tt> in <tt>sbbsdefs.js</tt> for bit definitions"
	, "Chat settings bit-flags - see <tt>CHAT_*</tt> in <tt>sbbsdefs.js</tt> for bit definitions"
	, "Mail settings bit-flags - see <tt>MAIL_*</tt> in <tt>sbbsdefs.js</tt> for bit definitions"
	, "Temporary file type (extension)"
	, "New file scan date/time (time_t format)"
	, "File transfer protocol (command key)"
	, "Logon time (time_t format)"
	, "Record is currently cached in memory"
	, "User has a System Operator's security level"
	, "Batch upload list file path/name"
	, "Batch download list file path/name"
	, NULL
static jsSyncPropertySpec js_user_security_properties[] = {
/*		 name				,tinyid					,flags,				ver	*/

	{   "password", USER_PROP_PASS, USER_PROP_FLAGS,   310 },
	{   "password_date", USER_PROP_PWMOD, USER_PROP_FLAGS,   310 },
	{   "level", USER_PROP_LEVEL, USER_PROP_FLAGS,   310 },
	{   "flags1", USER_PROP_FLAGS1, USER_PROP_FLAGS,   310 },
	{   "flags2", USER_PROP_FLAGS2, USER_PROP_FLAGS,   310 },
	{   "flags3", USER_PROP_FLAGS3, USER_PROP_FLAGS,   310 },
	{   "flags4", USER_PROP_FLAGS4, USER_PROP_FLAGS,   310 },
	{   "exemptions", USER_PROP_EXEMPT, USER_PROP_FLAGS,   310 },
	{   "restrictions", USER_PROP_REST, USER_PROP_FLAGS,   310 },
	{   "credits", USER_PROP_CDT, USER_PROP_FLAGS,   310 },
	{   "free_credits", USER_PROP_FREECDT, USER_PROP_FLAGS,   310 },
	{   "minutes", USER_PROP_MIN, USER_PROP_FLAGS,   310 },
	{   "extra_time", USER_PROP_TEXTRA, USER_PROP_FLAGS,   310 },
	{   "expiration_date", USER_PROP_EXPIRE, USER_PROP_FLAGS,   310 },
	{   "deletion_date", USER_PROP_DELDATE, JSPROP_ENUMERATE | JSPROP_READONLY, 32002 },
static const char*        user_security_prop_desc[] = {

	"Password"
	, "Date password last modified (time_t format)"
	, "Security level (0-99)"
	, "Flag set #1 (bit-flags) can use +/-[A-?] notation"
	, "Flag set #2 (bit-flags) can use +/-[A-?] notation"
	, "Flag set #3 (bit-flags) can use +/-[A-?] notation"
	, "Flag set #4 (bit-flags) can use +/-[A-?] notation"
	, "Exemption flags (bit-flags) can use +/-[A-?] notation"
	, "Restriction flags (bit-flags) can use +/-[A-?] notation"
	, "Credits"
	, "Free credits (for today only)"
	, "Extra minutes (time bank)"
	, "Extra minutes (for today only)"
	, "Expiration date/time (time_t format)"
	, "Deletion date/time (time_t format) - <small>READ ONLY</small>"
#define USER_PROP_FLAGS JSPROP_ENUMERATE | JSPROP_READONLY

/* user.limits: These should be READ ONLY by nature */
static jsSyncPropertySpec js_user_limits_properties[] = {
/*		 name					,tinyid					,flags,				ver	*/

	{   "time_per_logon", USER_PROP_TIMEPERCALL, USER_PROP_FLAGS,   311 },
	{   "time_per_day", USER_PROP_TIMEPERDAY, USER_PROP_FLAGS,   311 },
	{   "logons_per_day", USER_PROP_CALLSPERDAY, USER_PROP_FLAGS,   311 },
	{   "lines_per_message", USER_PROP_LINESPERMSG, USER_PROP_FLAGS,   311 },
	{   "email_per_day", USER_PROP_EMAILPERDAY, USER_PROP_FLAGS,   311 },
	{   "posts_per_day", USER_PROP_POSTSPERDAY, USER_PROP_FLAGS,   311 },
	{   "free_credits_per_day", USER_PROP_FREECDTPERDAY, USER_PROP_FLAGS,   311 },
static const char* user_limits_prop_desc[] = {
	"Time (in minutes) per logon"
	, "Time (in minutes) per day"
	, "Logons per day"
	, "Lines per message (post or email)"
	, "Email sent per day"
	, "Messages posted per day"
	, "Free credits given per day"
	, NULL
#define USER_PROP_FLAGS JSPROP_ENUMERATE | JSPROP_READONLY

/* user.stats: These should be READ ONLY by nature */
static jsSyncPropertySpec js_user_stats_properties[] = {
/*		 name				,tinyid					,flags,					ver	*/

	{   "laston_date", USER_PROP_LASTON, USER_PROP_FLAGS,       310 },