diff --git a/src/sbbs3/userdat.c b/src/sbbs3/userdat.c
index 3b6ce0c2b5bac9f040573d6882319319fcb65eba..a6a4cbc383ca3d6d6d7e7cd3a34c46968644f632 100644
--- a/src/sbbs3/userdat.c
+++ b/src/sbbs3/userdat.c
@@ -3214,3 +3214,57 @@ BOOL DLLCALL set_sysop_availability(scfg_t* scfg, BOOL available)
 		return ftouch(sysop_available_semfile(scfg));
 	return remove(sysop_available_semfile(scfg)) == 0;
 }
+
+/************************************/
+/* user .ini file get/set functions */
+/************************************/
+
+static FILE* user_ini_open(scfg_t* scfg, unsigned user_number, BOOL create)
+{
+	char path[MAX_PATH+1];
+
+	SAFEPRINTF2(path, "%suser/%04u.ini", scfg->data_dir, user_number);
+	return iniOpenFile(path, create);
+}
+
+BOOL DLLCALL user_get_property(scfg_t* scfg, unsigned user_number, const char* section, const char* key, char* value)
+{
+	FILE* fp;
+
+	fp = user_ini_open(scfg, user_number, /* create: */FALSE);
+	if(fp == NULL)
+		return FALSE;
+	char* result = iniReadValue(fp, section, key, NULL, value);
+	iniCloseFile(fp);
+	return result != NULL;
+}
+
+BOOL DLLCALL user_set_property(scfg_t* scfg, unsigned user_number, const char* section, const char* key, const char* value)
+{
+	FILE* fp;
+
+	fp = user_ini_open(scfg, user_number, /* create: */TRUE);
+	if(fp == NULL)
+		return FALSE;
+	str_list_t ini = iniReadFile(fp);
+	char* result = iniSetValue(&ini, section, key, value, /* style */NULL);
+	iniWriteFile(fp, ini);
+	iniFreeStringList(ini);
+	iniCloseFile(fp);
+	return result != NULL;
+}
+
+BOOL DLLCALL user_set_time_property(scfg_t* scfg, unsigned user_number, const char* section, const char* key, time_t value)
+{
+	FILE* fp;
+
+	fp = user_ini_open(scfg, user_number, /* create: */TRUE);
+	if(fp == NULL)
+		return FALSE;
+	str_list_t ini = iniReadFile(fp);
+	char* result = iniSetDateTime(&ini, section, key, /* include_time */TRUE, value, /* style */NULL);
+	iniWriteFile(fp, ini);
+	iniFreeStringList(ini);
+	iniCloseFile(fp);
+	return result != NULL;
+}
diff --git a/src/sbbs3/userdat.h b/src/sbbs3/userdat.h
index 2aec9f6e828489657f774e5b9e2cb2ed7331ba22..4b7d1e93211eb02a57d839971aa9b15496cdac17 100644
--- a/src/sbbs3/userdat.h
+++ b/src/sbbs3/userdat.h
@@ -126,6 +126,11 @@ DLLEXPORT BOOL	DLLCALL is_host_exempt(scfg_t*, const char* ip_addr, const char*
 DLLEXPORT BOOL	DLLCALL filter_ip(scfg_t*, const char* prot, const char* reason, const char* host
 								  ,const char* ip_addr, const char* username, const char* fname);
 
+/* user .ini file access */
+DLLEXPORT BOOL DLLCALL user_get_property(scfg_t*, unsigned user_number, const char* section, const char* key, char* value);
+DLLEXPORT BOOL DLLCALL user_set_property(scfg_t*, unsigned user_number, const char* section, const char* key, const char* value);
+DLLEXPORT BOOL DLLCALL user_set_time_property(scfg_t*, unsigned user_number, const char* section, const char* key, time_t);
+
 /* New-message-scan pointer functions: */
 DLLEXPORT BOOL	DLLCALL getmsgptrs(scfg_t*, user_t*, subscan_t*, void (*progress)(void*, int, int), void* cbdata);
 DLLEXPORT BOOL	DLLCALL putmsgptrs(scfg_t*, user_t*, subscan_t*);