diff --git a/ctrl/main.ini b/ctrl/main.ini
index 0edda5a710618a50ddd7e88d692fb3b238af626d..06c33f757ade3c1740d7dc9d55fa71a4fec4afa1 100644
--- a/ctrl/main.ini
+++ b/ctrl/main.ini
@@ -24,6 +24,8 @@ min_password_length=4
 max_log_size=0
 max_logs_kept=0
 ctrlkey_passthru=0
+max_getkey_inactivity=5M
+inactivity_warn=75
 user_backup_level=5
 mail_backup_level=5
 valuser=0
@@ -58,6 +60,8 @@ editor=FSEDITOR
 expiration_days=0
 command_shell=DEFAULT
 settings=0x6910
+chat_settings=0x10
+qwk_settings=0x10b
 download_protocol=Z
 msgscan_init=0
 gender_options=MFX
@@ -107,15 +111,21 @@ scanposts=
 scansubs=
 listmsgs=msglist
 textsec=text_sec
+chatsec=chat_sec
 automsg=automsg
-xtrnsec=xtrn_sec
+feedback=
 userlist=userlist
 nodelist=nodelist
 whosonline=nodelist -active
 privatemsg=privatemsg
 logonlist=logonlist
+xtrnsec=xtrn_sec
 prextrn=prextrn
 postxtrn=postxtrn
+scandirs=
+listfiles=
+fileinfo=
+batxfer=
 tempxfer=tempxfer
 [valset:0]
 level=10
diff --git a/ctrl/xtrn.ini b/ctrl/xtrn.ini
index 04ee305325d7b0cf7c20be996cd053220fab1de9..d5b60866f9311a764b55c26c0f11d7edaad79943 100644
--- a/ctrl/xtrn.ini
+++ b/ctrl/xtrn.ini
@@ -44,6 +44,7 @@ clean_cmd=
 startup_dir=
 textra=0
 max_time=0
+max_inactivity=0
 [prog:MAIN:SBBSLIST]
 name=Synchronet BBS List
 ars=
@@ -57,6 +58,7 @@ clean_cmd=
 startup_dir=
 textra=0
 max_time=0
+max_inactivity=0
 [prog:MAIN:AVATCHOO]
 name=Avatar Chooser
 ars=ANSI AND !GUEST
@@ -70,6 +72,7 @@ clean_cmd=
 startup_dir=
 textra=0
 max_time=0
+max_inactivity=0
 [prog:GAMES:MSWEEPER]
 name=Synchronet Minesweeper
 ars=
@@ -83,6 +86,7 @@ clean_cmd=
 startup_dir=../xtrn/minesweeper/
 textra=0
 max_time=0
+max_inactivity=0
 [prog:OPERATOR:SCFGANSI]
 name=Synchronet Configuration (ANSI)
 ars=ANSI
@@ -96,6 +100,7 @@ clean_cmd=
 startup_dir=
 textra=0
 max_time=0
+max_inactivity=0
 [prog:OPERATOR:SCFGDUMB]
 name=Synchronet Configuration (dumb)
 ars=
@@ -109,6 +114,7 @@ clean_cmd=
 startup_dir=
 textra=0
 max_time=0
+max_inactivity=0
 [prog:OPERATOR:CHKSETUP]
 name=Check Setup
 ars=
@@ -122,6 +128,7 @@ clean_cmd=
 startup_dir=
 textra=0
 max_time=0
+max_inactivity=0
 [prog:OPERATOR:FTNSETUP]
 name=Initial FidoNet Setup
 ars=
@@ -135,6 +142,7 @@ clean_cmd=
 startup_dir=
 textra=0
 max_time=0
+max_inactivity=0
 [prog:OPERATOR:FIDOCFGA]
 name=FidoNet Configuration (ANSI)
 ars=ANSI
@@ -148,6 +156,7 @@ clean_cmd=
 startup_dir=
 textra=0
 max_time=0
+max_inactivity=0
 [prog:OPERATOR:FIDOCFGD]
 name=FidoNet Configuration (dumb)
 ars=
@@ -161,6 +170,7 @@ clean_cmd=
 startup_dir=
 textra=0
 max_time=0
+max_inactivity=0
 [prog:OPERATOR:XSETUP]
 name=Auto-install New External Programs
 ars=
@@ -174,6 +184,7 @@ clean_cmd=
 startup_dir=
 textra=0
 max_time=0
+max_inactivity=0
 [event:FIDOIN]
 cmd=%!sbbsecho%. -ce
 days=128
diff --git a/exec/init-fidonet.js b/exec/init-fidonet.js
index ffc140aa95937817f84b695092a2574306da87c4..28f2c380ba2a2def1abff86078aedae39998a20c 100644
--- a/exec/init-fidonet.js
+++ b/exec/init-fidonet.js
@@ -613,7 +613,7 @@ if(your.node === temp_node && network.email && network.email.indexOf('@') > 0
 if(!find_sys_addr(fidoaddr.to_str(your))
 	&& confirm("Add node address " + fidoaddr.to_str(your) + " to your configuration")) {
 	var fido_addr_list = msgs_ini.iniGetValue("fidonet", "addr_list", []);
-	fido_addr_list.push(your);
+	fido_addr_list.push(fidoaddr.to_str(your));
 	msgs_ini.iniSetValue("fidonet", "addr_list", fido_addr_list);
 }
 
diff --git a/node1/node.ini b/node1/node.ini
index 85f13e34cc9032a23890e47a150d8ccf5ca5307c..11bf22013e06215e44dc68471b208fb0f8be2c86 100644
--- a/node1/node.ini
+++ b/node1/node.ini
@@ -1,27 +1,8 @@
-number=1
-name=Node 1
-phone=XXX-XXX-XXXX
-comspec=
-settings=33298
-ivt=1
-swap=2
-swapdir=
-valuser=1
-minbps=300
-ars=
-dollars_per_call=0
-editor=
-viewer=%!list %f
+phone=N/A
 daily=
-scrnlen=0
-scrnblank=0
 text_dir=../text/
-temp_dir=temp\
-mdm_hang=
-sem_check=5
-stat_check=5
-scfg_cmd=%!scfg %k /t%w
-sec_warn=180
-sec_hangup=300
-erruser=0
-errlevel=2
+temp_dir=temp
+ars=
+settings=0
+sem_check=0
+stat_check=0
diff --git a/src/sbbs3/ringbuf.c b/src/sbbs3/ringbuf.c
index a982ba9138802522dc49365f1df9fff307b9ec37..a0745bf6a09861552eaef2a8fb81d0a4ce918c51 100644
--- a/src/sbbs3/ringbuf.c
+++ b/src/sbbs3/ringbuf.c
@@ -1,9 +1,5 @@
-/* ringbuf.c */
-
 /* Synchronet ring buffer routines */
 
-/* $Id: ringbuf.c,v 1.32 2019/08/26 23:37:52 rswindell Exp $ */
-
 /****************************************************************************
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
  * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
@@ -17,21 +13,9 @@
  * See the GNU General Public License for more details: gpl.txt or			*
  * http://www.fsf.org/copyleft/gpl.html										*
  *																			*
- * Anonymous FTP access to the most recent released source is available at	*
- * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
- *																			*
- * Anonymous CVS access to the development source and modification history	*
- * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
- * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
- *     (just hit return, no password is necessary)							*
- * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
- *																			*
  * For Synchronet coding style and modification guidelines, see				*
  * http://www.synchro.net/source.html										*
  *																			*
- * You are encouraged to submit any modifications (preferably in Unix diff	*
- * format) via e-mail to mods@synchro.net									*
- *																			*
  * Note: If this box doesn't appear square, then you need to fix your tabs.	*
  ****************************************************************************/
 
@@ -96,9 +80,9 @@ int RingBufInit( RingBuf* rb, DWORD size
 	rb->pEnd=rb->pStart+size;
     rb->size=size;
 #ifdef RINGBUF_EVENT
-	rb->data_event=CreateEvent(NULL,TRUE,TRUE,NULL);
-	rb->highwater_event=CreateEvent(NULL,TRUE,TRUE,NULL);
-	rb->empty_event=CreateEvent(NULL,TRUE,TRUE,NULL);
+	rb->data_event=CreateEvent(NULL, /*  ManualReset: */TRUE, /* InitialState: */FALSE, NULL);
+	rb->highwater_event=CreateEvent(NULL, /*  ManualReset: */TRUE, /* InitialState: */FALSE, NULL);
+	rb->empty_event=CreateEvent(NULL, /* ManualReset: */TRUE, /* InitialState: */TRUE, NULL);
 #endif
 #ifdef RINGBUF_MUTEX
 	pthread_mutex_init(&rb->mutex,NULL);
diff --git a/src/sbbs3/sbbs_ini.c b/src/sbbs3/sbbs_ini.c
index 4fa57207e379b7c5b1534fab1e30e3f547ec1108..b7421ef1a9d974e0af59e2439c2076dc98628c47 100644
--- a/src/sbbs3/sbbs_ini.c
+++ b/src/sbbs3/sbbs_ini.c
@@ -79,10 +79,10 @@ void sbbs_get_ini_fname(char* ini_file, const char* ctrl_dir)
 {
 	/* pHostName is no longer used since iniFileName calls gethostname() itself */
 
-#if defined(_WINSOCKAPI_)	 
-	WSADATA WSAData;	 
-    (void)WSAStartup(MAKEWORD(1,1), &WSAData); /* req'd for gethostname */	 
-#endif	 
+#if defined(_WINSOCKAPI_)
+	WSADATA WSAData;
+    (void)WSAStartup(MAKEWORD(1,1), &WSAData); /* req'd for gethostname */
+#endif
 
 #if defined(__unix__) && defined(PREFIX)
 	sprintf(ini_file,PREFIX"/etc/sbbs.ini");
@@ -91,8 +91,8 @@ void sbbs_get_ini_fname(char* ini_file, const char* ctrl_dir)
 #endif
 	iniFileName(ini_file,MAX_PATH,ctrl_dir,"sbbs.ini");
 
-#if defined(_WINSOCKAPI_)	 
-	WSACleanup();	 
+#if defined(_WINSOCKAPI_)
+	WSACleanup();
 #endif
 }
 
@@ -100,8 +100,7 @@ static BOOL iniSetStringWithGlobalDefault(str_list_t* lp, const char* section, c
 	,const char* value, const char* global_value, ini_style_t* style)
 {
 	if(value != global_value && strcmp(value, global_value) == 0) {
-		iniRemoveKey(lp, section, key);
-		return iniKeyExists(*lp, section, key) == FALSE;
+		return iniKeyExists(*lp, section, key) == FALSE || iniRemoveValue(lp, section, key) == TRUE;
 	}
 	return iniSetString(lp, section, key, value, style) != NULL;
 }
@@ -172,12 +171,12 @@ BOOL sbbs_set_js_settings(
 
 	if(js->gc_interval==defaults->gc_interval)
 		iniRemoveValue(lp,section,strJavaScriptGcInterval);
-	else 
+	else
 		failure|=iniSetInteger(lp,section,strJavaScriptGcInterval,js->gc_interval,style)==NULL;
 
 	if(js->yield_interval==defaults->yield_interval)
 		iniRemoveValue(lp,section,strJavaScriptYieldInterval);
-	else 
+	else
 		failure|=iniSetInteger(lp,section,strJavaScriptYieldInterval,js->yield_interval,style)==NULL;
 
 	if(strcmp(js->load_path,defaults->load_path)==0)
@@ -253,12 +252,13 @@ static void set_login_attempt_settings(str_list_t* lp, const char* section, stru
 	iniSetInteger(lp,section,strLoginAttemptFilterThreshold,settings.filter_threshold,&style);
 }
 
+static const struct in6_addr wildcard6;
+
 static void get_ini_globals(str_list_t list, global_startup_t* global)
 {
 	const char* section = "Global";
 	char		value[INI_MAX_VALUE_LEN];
 	char*		p;
-	struct in6_addr	wildcard6 = {{{0}}};
 
 	p=iniGetString(list,section,strCtrlDirectory,nulstr,value);
 	if(*p) {
@@ -276,10 +276,9 @@ static void get_ini_globals(str_list_t list, global_startup_t* global)
 	if(*p)
         SAFECOPY(global->host_name,value);
 
-	global->sem_chk_freq=iniGetShortInt(list,section,strSemFileCheckFrequency,DEFAULT_SEM_CHK_FREQ);
-	iniFreeStringList(global->interfaces);
+	global->sem_chk_freq=iniGetUInteger(list,section,strSemFileCheckFrequency,DEFAULT_SEM_CHK_FREQ);
 	global->interfaces=iniGetStringList(list,section,strInterfaces, ",", "0.0.0.0,::");
-	global->outgoing4.s_addr=iniGetIpAddress(list,section,strOutgoing4,0);
+	global->outgoing4.s_addr=iniGetIpAddress(list,section,strOutgoing4,INADDR_ANY);
 	global->outgoing6=iniGetIp6Address(list,section,strOutgoing6,wildcard6);
 	global->log_level=iniGetLogLevel(list,section,strLogLevel,DEFAULT_LOG_LEVEL);
 	global->tls_error_level=iniGetLogLevel(list,section, strTLSErrorLevel, 0);
@@ -299,6 +298,40 @@ static void get_ini_globals(str_list_t list, global_startup_t* global)
 	sbbs_get_sound_settings(list, section, &global->sound, &global->sound);
 }
 
+void sbbs_free_ini(
+	 global_startup_t*		global
+	,bbs_startup_t*			bbs
+	,ftp_startup_t*			ftp
+	,web_startup_t*			web
+	,mail_startup_t*		mail
+	,services_startup_t*	services
+	)
+{
+	if(global != NULL) {
+		iniFreeStringList(global->interfaces);
+	}
+	if(bbs != NULL) {
+		iniFreeStringList(bbs->telnet_interfaces);
+		iniFreeStringList(bbs->rlogin_interfaces);
+		iniFreeStringList(bbs->ssh_interfaces);
+	}
+	if(web != NULL) {
+		iniFreeStringList(web->interfaces);
+		iniFreeStringList(web->tls_interfaces);
+		iniFreeStringList(web->index_file_name);
+		iniFreeStringList(web->cgi_ext);
+	}
+	if(ftp != NULL) {
+		iniFreeStringList(ftp->interfaces);
+	}
+	if(mail != NULL) {
+		iniFreeStringList(mail->interfaces);
+		iniFreeStringList(mail->pop3_interfaces);
+	}
+	if(services != NULL) {
+		iniFreeStringList(services->interfaces);
+	}
+}
 
 void sbbs_read_ini(
 	 FILE*					fp
@@ -310,7 +343,7 @@ void sbbs_read_ini(
 	,ftp_startup_t*			ftp
 	,BOOL*					run_web
 	,web_startup_t*			web
-	,BOOL*					run_mail		
+	,BOOL*					run_mail
 	,mail_startup_t*		mail
 	,BOOL*					run_services
 	,services_startup_t*	services
@@ -335,6 +368,14 @@ void sbbs_read_ini(
 		global=&global_buf;
 	}
 
+	sbbs_free_ini(global
+		,bbs
+		,ftp
+		,web
+		,mail
+		,services
+		);
+
 	list=iniReadFile(fp);
 
 	get_ini_globals(list, global);
@@ -371,14 +412,12 @@ void sbbs_read_ini(
 			=iniGetIp6Address(list,section,strOutgoing6,global->outgoing6);
 
 		bbs->telnet_port
-			=iniGetShortInt(list,section,"TelnetPort",IPPORT_TELNET);
-		iniFreeStringList(bbs->telnet_interfaces);
+			=iniGetUInt16(list,section,"TelnetPort",IPPORT_TELNET);
 		bbs->telnet_interfaces
 			=iniGetStringList(list,section,"TelnetInterface",",",global_interfaces);
 
 		bbs->rlogin_port
 			=iniGetShortInt(list,section,"RLoginPort",513);
-		iniFreeStringList(bbs->rlogin_interfaces);
 		bbs->rlogin_interfaces
 			=iniGetStringList(list,section,"RLoginInterface",",",global_interfaces);
 
@@ -391,7 +430,6 @@ void sbbs_read_ini(
 			=iniGetShortInt(list,section,"SSHPort",22);
 		bbs->ssh_connect_timeout
 			=iniGetShortInt(list,section,"SSHConnectTimeout",10);
-		iniFreeStringList(bbs->ssh_interfaces);
 		bbs->ssh_interfaces
 			=iniGetStringList(list,section,"SSHInterface",",",global_interfaces);
 
@@ -440,7 +478,7 @@ void sbbs_read_ini(
 		default_dosemuconf_path="";
 
 		SAFECOPY(bbs->dosemuconf_path
-			,iniGetString(list,section,"DOSemuConfPath",default_dosemuconf_path,value));			
+			,iniGetString(list,section,"DOSemuConfPath",default_dosemuconf_path,value));
 	#endif
 		bbs->usedosemu=iniGetBool(list,section,"UseDOSemu",TRUE);
 		SAFECOPY(bbs->dosemu_path
@@ -482,7 +520,6 @@ void sbbs_read_ini(
 			=iniGetIp6Address(list,section,strOutgoing6,global->outgoing6);
 		ftp->port
 			=iniGetShortInt(list,section,strPort,IPPORT_FTP);
-		iniFreeStringList(ftp->interfaces);
 		ftp->interfaces
 			=iniGetStringList(list,section,strInterfaces,",",global_interfaces);
 		ftp->max_clients
@@ -539,7 +576,6 @@ void sbbs_read_ini(
 
 	if(mail!=NULL) {
 
-		iniFreeStringList(mail->interfaces);
 		mail->interfaces
 			=iniGetStringList(list,section,strInterfaces,",",global_interfaces);
 		mail->outgoing4.s_addr
@@ -552,7 +588,6 @@ void sbbs_read_ini(
 			=iniGetShortInt(list,section,"SubmissionPort",IPPORT_SUBMISSION);
 		mail->submissions_port
 			=iniGetShortInt(list,section,"TLSSubmissionPort",IPPORT_SUBMISSIONS);
-		iniFreeStringList(mail->pop3_interfaces);
 		mail->pop3_interfaces
 			=iniGetStringList(list,section,"POP3Interface",",",global_interfaces);
 		mail->pop3_port
@@ -566,15 +601,15 @@ void sbbs_read_ini(
 		mail->max_inactivity
 			=(uint16_t)iniGetDuration(list,section,strMaxInactivity,MAIL_DEFAULT_MAX_INACTIVITY);		/* seconds */
 		mail->max_delivery_attempts
-			=iniGetShortInt(list,section,"MaxDeliveryAttempts",MAIL_DEFAULT_MAX_DELIVERY_ATTEMPTS);
+			=iniGetUInteger(list,section,"MaxDeliveryAttempts",MAIL_DEFAULT_MAX_DELIVERY_ATTEMPTS);
 		mail->rescan_frequency
-			=iniGetShortInt(list,section,"RescanFrequency",MAIL_DEFAULT_RESCAN_FREQUENCY);	/* 60 minutes */
+			=iniGetUInteger(list,section,"RescanFrequency",MAIL_DEFAULT_RESCAN_FREQUENCY);	/* 60 minutes */
 		mail->sem_chk_freq
-			=iniGetShortInt(list,section,strSemFileCheckFrequency,global->sem_chk_freq);
+			=iniGetUInteger(list,section,strSemFileCheckFrequency,global->sem_chk_freq);
 		mail->lines_per_yield
-			=iniGetShortInt(list,section,"LinesPerYield",MAIL_DEFAULT_LINES_PER_YIELD);
+			=iniGetUInteger(list,section,"LinesPerYield",MAIL_DEFAULT_LINES_PER_YIELD);
 		mail->max_recipients
-			=iniGetShortInt(list,section,"MaxRecipients",MAIL_DEFAULT_MAX_RECIPIENTS);
+			=iniGetUInteger(list,section,"MaxRecipients",MAIL_DEFAULT_MAX_RECIPIENTS);
 		mail->max_msg_size
 			=(DWORD)iniGetBytes(list,section,"MaxMsgSize",/* units: */1,MAIL_DEFAULT_MAX_MSG_SIZE);
 		mail->max_msgs_waiting
@@ -639,7 +674,6 @@ void sbbs_read_ini(
 
 	if(services!=NULL) {
 
-		iniFreeStringList(services->interfaces);
 		services->interfaces
 			=iniGetStringList(list,section,strInterfaces,",",global_interfaces);
 		services->outgoing4.s_addr
@@ -648,7 +682,7 @@ void sbbs_read_ini(
 			=iniGetIp6Address(list,section,strOutgoing6,global->outgoing6);
 
 		services->sem_chk_freq
-			=iniGetShortInt(list,section,strSemFileCheckFrequency,global->sem_chk_freq);
+			=iniGetUInteger(list,section,strSemFileCheckFrequency,global->sem_chk_freq);
 
 		/* JavaScript operating parameters */
 		sbbs_get_js_settings(list, section, &services->js, &global->js);
@@ -683,22 +717,20 @@ void sbbs_read_ini(
 
 	if(web!=NULL) {
 
-		iniFreeStringList(web->interfaces);
 		web->interfaces
 			=iniGetStringList(list,section,strInterfaces,",",global_interfaces);
-		iniFreeStringList(web->tls_interfaces);
 		web->tls_interfaces
 			=iniGetStringList(list,section,"TLSInterface",",",global_interfaces);
 		web->port
-			=iniGetShortInt(list,section,strPort,IPPORT_HTTP);
+			=iniGetUInt16(list,section,strPort,IPPORT_HTTP);
 		web->tls_port
-			=iniGetShortInt(list,section,"TLSPort",IPPORT_HTTPS);
+			=iniGetUInt16(list,section,"TLSPort",IPPORT_HTTPS);
 		web->max_clients
-			=iniGetShortInt(list,section,strMaxClients,WEB_DEFAULT_MAX_CLIENTS);
+			=iniGetUInteger(list,section,strMaxClients,WEB_DEFAULT_MAX_CLIENTS);
 		web->max_inactivity
 			=(uint16_t)iniGetDuration(list,section,strMaxInactivity,WEB_DEFAULT_MAX_INACTIVITY);		/* seconds */
 		web->sem_chk_freq
-			=iniGetShortInt(list,section,strSemFileCheckFrequency,global->sem_chk_freq);
+			=iniGetUInteger(list,section,strSemFileCheckFrequency,global->sem_chk_freq);
 
 		/* JavaScript operating parameters */
 		sbbs_get_js_settings(list, section, &web->js, &global->js);
@@ -728,10 +760,8 @@ void sbbs_read_ini(
 		SAFECOPY(web->default_cgi_content
 			,iniGetString(list,section,"DefaultCGIContent",WEB_DEFAULT_CGI_CONTENT,value));
 
-		iniFreeStringList(web->index_file_name);
 		web->index_file_name
 			=iniGetStringList(list,section,"IndexFileNames", "," ,"index.html,index.ssjs");
-		iniFreeStringList(web->cgi_ext);
 		web->cgi_ext
 			=iniGetStringList(list,section,"CGIExtensions", "," ,".cgi");
 		SAFECOPY(web->ssjs_ext
@@ -750,7 +780,7 @@ void sbbs_read_ini(
 			=iniGetBitField(list,section,strOptions,web_options
 				,BBS_OPT_NO_HOST_LOOKUP | WEB_OPT_HTTP_LOGGING);
 		web->outbuf_drain_timeout
-			=iniGetShortInt(list,section,"OutbufDrainTimeout",10);
+			=iniGetUInteger(list,section,"OutbufDrainTimeout",10);
 
 		web->bind_retry_count=iniGetInteger(list,section,strBindRetryCount,global->bind_retry_count);
 		web->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,global->bind_retry_delay);
@@ -771,7 +801,7 @@ BOOL sbbs_write_ini(
 	,ftp_startup_t*			ftp
 	,BOOL					run_web
 	,web_startup_t*			web
-	,BOOL					run_mail		
+	,BOOL					run_mail
 	,mail_startup_t*		mail
 	,BOOL					run_services
 	,services_startup_t*	services
@@ -798,7 +828,7 @@ BOOL sbbs_write_ini(
 		get_ini_globals(list, &global_buf);
 		global = &global_buf;
 	}
-	
+
 	lp=&list;
 
 	do { /* try */
@@ -810,9 +840,11 @@ BOOL sbbs_write_ini(
 		iniSetString(lp,section,strCtrlDirectory,global->ctrl_dir,&style);
 		iniSetString(lp,section,strTempDirectory,global->temp_dir,&style);
 		iniSetString(lp,section,strHostName,global->host_name,&style);
-		iniSetShortInt(lp,section,strSemFileCheckFrequency,global->sem_chk_freq,&style);
-		iniSetIpAddress(lp,section,strOutgoing4,global->outgoing4.s_addr,&style);
-		iniSetIp6Address(lp,section,strOutgoing6,global->outgoing6,&style);
+		iniSetUInteger(lp,section,strSemFileCheckFrequency,global->sem_chk_freq,&style);
+		if(global->outgoing4.s_addr != INADDR_ANY)
+			iniSetIpAddress(lp,section,strOutgoing4,global->outgoing4.s_addr,&style);
+		if(memcmp(&global->outgoing6, &wildcard6, sizeof(wildcard6)) != 0)
+			iniSetIp6Address(lp,section,strOutgoing6,global->outgoing6,&style);
 		iniSetStringList(lp, section, strInterfaces, ",", global->interfaces, &style);
 		iniSetLogLevel(lp,section,strLogLevel,global->log_level,&style);
 		iniSetLogLevel(lp,section,strTLSErrorLevel,global->tls_error_level,&style);
@@ -841,37 +873,37 @@ BOOL sbbs_write_ini(
 		else if(!iniSetStringList(lp,section,"TelnetInterface", ",", bbs->telnet_interfaces, &style))
 			break;
 
-		if(!iniSetShortInt(lp,section,"TelnetPort",bbs->telnet_port,&style))
+		if(!iniSetUInt16(lp,section,"TelnetPort",bbs->telnet_port,&style))
 			break;
 
 		if(strListCmp(bbs->rlogin_interfaces, global->interfaces)==0)
 			iniRemoveValue(lp,section,"RLoginInterface");
 		else if(!iniSetStringList(lp,section,"RLoginInterface", ",", bbs->rlogin_interfaces,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"RLoginPort",bbs->rlogin_port,&style))
+		if(!iniSetUInt16(lp,section,"RLoginPort",bbs->rlogin_port,&style))
 			break;
 
-		if(!iniSetShortInt(lp,section,"Pet40Port",bbs->pet40_port,&style))
+		if(!iniSetUInt16(lp,section,"Pet40Port",bbs->pet40_port,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"Pet80Port",bbs->pet80_port,&style))
+		if(!iniSetUInt16(lp,section,"Pet80Port",bbs->pet80_port,&style))
 			break;
 
 		if(strListCmp(bbs->ssh_interfaces, global->interfaces)==0)
 			iniRemoveValue(lp,section,"SSHInterface");
 		else if(!iniSetStringList(lp,section,"SSHInterface", ",", bbs->ssh_interfaces,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"SSHPort",bbs->ssh_port,&style))
+		if(!iniSetUInt16(lp,section,"SSHPort",bbs->ssh_port,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"SSHConnectTimeout",bbs->ssh_connect_timeout,&style))
+		if(!iniSetUInteger(lp,section,"SSHConnectTimeout",bbs->ssh_connect_timeout,&style))
 			break;
 
-		if(!iniSetShortInt(lp,section,"FirstNode",bbs->first_node,&style))
+		if(!iniSetUInteger(lp,section,"FirstNode",bbs->first_node,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"LastNode",bbs->last_node,&style))
+		if(!iniSetUInteger(lp,section,"LastNode",bbs->last_node,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"OutbufHighwaterMark",bbs->outbuf_highwater_mark,&style))
+		if(!iniSetUInteger(lp,section,"OutbufHighwaterMark",bbs->outbuf_highwater_mark,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"OutbufDrainTimeout",bbs->outbuf_drain_timeout,&style))
+		if(!iniSetUInteger(lp,section,"OutbufDrainTimeout",bbs->outbuf_drain_timeout,&style))
 			break;
 		if(!iniSetInteger(lp,section,strMaxConConn,bbs->max_concurrent_connections,&style))
 			break;
@@ -884,7 +916,7 @@ BOOL sbbs_write_ini(
 
 		if(bbs->sem_chk_freq==global->sem_chk_freq)
 			iniRemoveValue(lp,section,strSemFileCheckFrequency);
-		else if(!iniSetShortInt(lp,section,strSemFileCheckFrequency,bbs->sem_chk_freq,&style))
+		else if(!iniSetUInteger(lp,section,strSemFileCheckFrequency,bbs->sem_chk_freq,&style))
 			break;
 
 		if(bbs->log_level==global->log_level)
@@ -958,20 +990,20 @@ BOOL sbbs_write_ini(
 		else if(!iniSetIpAddress(lp, section, strOutgoing4, ftp->outgoing4.s_addr, &style))
 			break;
 
-		if(memcmp(&ftp->outgoing6, &global->outgoing6, sizeof(ftp->outgoing6)))
+		if(memcmp(&ftp->outgoing6, &global->outgoing6, sizeof(ftp->outgoing6)) == 0)
 			iniRemoveValue(lp,section,strOutgoing6);
 		else if(!iniSetIp6Address(lp, section, strOutgoing6, ftp->outgoing6, &style))
 			break;
 
-		if(!iniSetShortInt(lp,section,strPort,ftp->port,&style))
+		if(!iniSetUInt16(lp,section,strPort,ftp->port,&style))
 			break;
-		if(!iniSetShortInt(lp,section,strMaxClients,ftp->max_clients,&style))
+		if(!iniSetUInteger(lp,section,strMaxClients,ftp->max_clients,&style))
 			break;
 		if(!iniSetDuration(lp,section,strMaxInactivity,ftp->max_inactivity,&style))
 			break;
 		if(!iniSetInteger(lp,section,strMaxConConn,ftp->max_concurrent_connections,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"QwkTimeout",ftp->qwk_timeout,&style))
+		if(!iniSetUInteger(lp,section,"QwkTimeout",ftp->qwk_timeout,&style))
 			break;
 		if(!iniSetBytes(lp,section,"MinFileSize",1,ftp->min_fsize,&style))
 			break;
@@ -983,14 +1015,14 @@ BOOL sbbs_write_ini(
 			break;
 		if(!iniSetIp6Address(lp,section,"PasvIp6Address",ftp->pasv_ip6_addr,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"PasvPortLow",ftp->pasv_port_low,&style))
+		if(!iniSetUInt16(lp,section,"PasvPortLow",ftp->pasv_port_low,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"PasvPortHigh",ftp->pasv_port_high,&style))
+		if(!iniSetUInt16(lp,section,"PasvPortHigh",ftp->pasv_port_high,&style))
 			break;
 
 		if(ftp->sem_chk_freq==global->sem_chk_freq)
 			iniRemoveValue(lp,section,strSemFileCheckFrequency);
-		else if(!iniSetShortInt(lp,section,strSemFileCheckFrequency,ftp->sem_chk_freq,&style))
+		else if(!iniSetUInteger(lp,section,strSemFileCheckFrequency,ftp->sem_chk_freq,&style))
 			break;
 
 		if(ftp->log_level==global->log_level)
@@ -1014,7 +1046,7 @@ BOOL sbbs_write_ini(
 
 		if(!sbbs_set_sound_settings(lp, section, &ftp->sound, &global->sound, &style))
 			break;
-	
+
 		if(!iniSetBitField(lp,section,strOptions,ftp_options,ftp->options,&style))
 			break;
 
@@ -1046,14 +1078,14 @@ BOOL sbbs_write_ini(
 		else if(!iniSetIpAddress(lp, section, strOutgoing4, mail->outgoing4.s_addr, &style))
 			break;
 
-		if(memcmp(&mail->outgoing6, &global->outgoing6, sizeof(ftp->outgoing6)))
+		if(memcmp(&mail->outgoing6, &global->outgoing6, sizeof(ftp->outgoing6)) == 0)
 			iniRemoveValue(lp,section,strOutgoing6);
 		else if(!iniSetIp6Address(lp, section, strOutgoing6, mail->outgoing6, &style))
 			break;
 
 		if(mail->sem_chk_freq==global->sem_chk_freq)
 			iniRemoveValue(lp,section,strSemFileCheckFrequency);
-		else if(!iniSetShortInt(lp,section,strSemFileCheckFrequency,mail->sem_chk_freq,&style))
+		else if(!iniSetUInteger(lp,section,strSemFileCheckFrequency,mail->sem_chk_freq,&style))
 			break;
 
 		if(mail->log_level==global->log_level)
@@ -1064,29 +1096,29 @@ BOOL sbbs_write_ini(
 			iniRemoveValue(lp,section,strTLSErrorLevel);
 		else if(!iniSetLogLevel(lp,section,strTLSErrorLevel,mail->tls_error_level,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"SMTPPort",mail->smtp_port,&style))
+		if(!iniSetUInt16(lp,section,"SMTPPort",mail->smtp_port,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"SubmissionPort",mail->submission_port,&style))
+		if(!iniSetUInt16(lp,section,"SubmissionPort",mail->submission_port,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"TLSSubmissionPort",mail->submissions_port,&style))
+		if(!iniSetUInt16(lp,section,"TLSSubmissionPort",mail->submissions_port,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"POP3Port",mail->pop3_port,&style))
+		if(!iniSetUInt16(lp,section,"POP3Port",mail->pop3_port,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"TLSPOP3Port",mail->pop3s_port,&style))
+		if(!iniSetUInt16(lp,section,"TLSPOP3Port",mail->pop3s_port,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"RelayPort",mail->relay_port,&style))
+		if(!iniSetUInt16(lp,section,"RelayPort",mail->relay_port,&style))
 			break;
-		if(!iniSetShortInt(lp,section,strMaxClients,mail->max_clients,&style))
+		if(!iniSetUInteger(lp,section,strMaxClients,mail->max_clients,&style))
 			break;
 		if(!iniSetDuration(lp,section,strMaxInactivity,mail->max_inactivity,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"MaxDeliveryAttempts",mail->max_delivery_attempts,&style))
+		if(!iniSetUInteger(lp,section,"MaxDeliveryAttempts",mail->max_delivery_attempts,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"RescanFrequency",mail->rescan_frequency,&style))
+		if(!iniSetUInteger(lp,section,"RescanFrequency",mail->rescan_frequency,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"LinesPerYield",mail->lines_per_yield,&style))
+		if(!iniSetUInteger(lp,section,"LinesPerYield",mail->lines_per_yield,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"MaxRecipients",mail->max_recipients,&style))
+		if(!iniSetUInteger(lp,section,"MaxRecipients",mail->max_recipients,&style))
 			break;
 		if(!iniSetBytes(lp,section,"MaxMsgSize",/* unit: */1,mail->max_msg_size,&style))
 			break;
@@ -1171,14 +1203,14 @@ BOOL sbbs_write_ini(
 		else if(!iniSetIpAddress(lp, section, strOutgoing4, services->outgoing4.s_addr, &style))
 			break;
 
-		if(memcmp(&services->outgoing6, &global->outgoing6, sizeof(ftp->outgoing6)))
+		if(memcmp(&services->outgoing6, &global->outgoing6, sizeof(ftp->outgoing6)) == 0)
 			iniRemoveValue(lp,section,strOutgoing6);
 		else if(!iniSetIp6Address(lp, section, strOutgoing6, services->outgoing6, &style))
 			break;
 
 		if(services->sem_chk_freq==global->sem_chk_freq)
 			iniRemoveValue(lp,section,strSemFileCheckFrequency);
-		else if(!iniSetShortInt(lp,section,strSemFileCheckFrequency,services->sem_chk_freq,&style))
+		else if(!iniSetUInteger(lp,section,strSemFileCheckFrequency,services->sem_chk_freq,&style))
 			break;
 
 		if(services->log_level==global->log_level)
@@ -1237,12 +1269,11 @@ BOOL sbbs_write_ini(
 			iniRemoveValue(lp,section,"TLSInterface");
 		else if(!iniSetStringList(lp,section,"TLSInterface",",",web->tls_interfaces,&style))
 			break;
-
-		if(!iniSetShortInt(lp,section,strPort,web->port,&style))
+		if(!iniSetUInt16(lp,section,strPort,web->port,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"TLSPort",web->tls_port,&style))
+		if(!iniSetUInt16(lp,section,"TLSPort",web->tls_port,&style))
 			break;
-		if(!iniSetShortInt(lp,section,strMaxClients,web->max_clients,&style))
+		if(!iniSetUInteger(lp,section,strMaxClients,web->max_clients,&style))
 			break;
 		if(!iniSetDuration(lp,section,strMaxInactivity,web->max_inactivity,&style))
 			break;
@@ -1251,7 +1282,7 @@ BOOL sbbs_write_ini(
 
 		if(web->sem_chk_freq==global->sem_chk_freq)
 			iniRemoveValue(lp,section,strSemFileCheckFrequency);
-		else if(!iniSetShortInt(lp,section,strSemFileCheckFrequency,web->sem_chk_freq,&style))
+		else if(!iniSetUInteger(lp,section,strSemFileCheckFrequency,web->sem_chk_freq,&style))
 			break;
 
 		if(web->log_level==global->log_level)
@@ -1320,7 +1351,7 @@ BOOL sbbs_write_ini(
 			iniRemoveValue(lp,section,strBindRetryDelay);
 		else if(!iniSetInteger(lp,section,strBindRetryDelay,web->bind_retry_delay,&style))
 			break;
-		if(!iniSetShortInt(lp,section,"OutbufDrainTimeout",web->outbuf_drain_timeout,&style))
+		if(!iniSetUInteger(lp,section,"OutbufDrainTimeout",web->outbuf_drain_timeout,&style))
 			break;
 	}
 
diff --git a/src/sbbs3/sbbs_ini.h b/src/sbbs3/sbbs_ini.h
index 2aaecba4b1e6b6af92790568f69638fd876f1e3e..9a7b3f8dac7ed05b4e26c41e37e53b0be4e6a647 100644
--- a/src/sbbs3/sbbs_ini.h
+++ b/src/sbbs3/sbbs_ini.h
@@ -49,12 +49,21 @@ void sbbs_read_ini(
 	,ftp_startup_t*			ftp_startup
 	,BOOL*					run_web
 	,web_startup_t*			web_startup
-	,BOOL*					run_mail		
+	,BOOL*					run_mail
 	,mail_startup_t*		mail_startup
 	,BOOL*					run_services
 	,services_startup_t*	services_startup
 	);
 
+void sbbs_free_ini(
+	 global_startup_t*		global
+	,bbs_startup_t*			bbs_startup
+	,ftp_startup_t*			ftp_startup
+	,web_startup_t*			web_startup
+	,mail_startup_t*		mail_startup
+	,services_startup_t*	services_startup
+	);
+
 void sbbs_get_js_settings(
 	 str_list_t list
 	,const char* section
@@ -80,7 +89,7 @@ BOOL sbbs_write_ini(
 	,ftp_startup_t*			ftp
 	,BOOL					run_web
 	,web_startup_t*			web
-	,BOOL					run_mail		
+	,BOOL					run_mail
 	,mail_startup_t*		mail
 	,BOOL					run_services
 	,services_startup_t*	services
diff --git a/src/sbbs3/scfg/objects.mk b/src/sbbs3/scfg/objects.mk
index 93fe2bb5f91b55264ca6c1a4c74d4c370330b6ba..be3d7aaac8aceb3ab1361ab287e90758b1c6109e 100644
--- a/src/sbbs3/scfg/objects.mk
+++ b/src/sbbs3/scfg/objects.mk
@@ -9,6 +9,7 @@ OBJS =	$(MTOBJODIR)/scfg$(OFILE)\
 	$(MTOBJODIR)/scfgmsg$(OFILE)\
 	$(MTOBJODIR)/scfgnet$(OFILE)\
 	$(MTOBJODIR)/scfgnode$(OFILE)\
+	$(MTOBJODIR)/scfgsrvr$(OFILE)\
 	$(MTOBJODIR)/scfgsub$(OFILE)\
 	$(MTOBJODIR)/scfgsys$(OFILE)\
 	$(MTOBJODIR)/scfgxfr1$(OFILE)\
@@ -18,6 +19,7 @@ OBJS =	$(MTOBJODIR)/scfg$(OFILE)\
 	$(MTOBJODIR)/scfglib1$(OFILE)\
 	$(MTOBJODIR)/scfglib2$(OFILE)\
 	$(MTOBJODIR)/getctrl$(OFILE)\
+	$(MTOBJODIR)/sbbs_ini$(OFILE)\
 	$(MTOBJODIR)/load_cfg$(OFILE)\
 	$(MTOBJODIR)/readtext$(OFILE)\
 	$(MTOBJODIR)/text_defaults$(OFILE)\
diff --git a/src/sbbs3/scfg/scfg.c b/src/sbbs3/scfg/scfg.c
index 6ff22e049e6cdf71ee80c18b0e437c6c28e596fb..596a5247e5fcafb3d2fa1c903631cb3617395641 100644
--- a/src/sbbs3/scfg/scfg.c
+++ b/src/sbbs3/scfg/scfg.c
@@ -27,6 +27,7 @@
 #include "git_branch.h"
 #include "cryptlib.h"
 #include "xpdatetime.h"
+#include "sbbs_ini.h"
 
 /********************/
 /* Global Variables */
@@ -362,12 +363,23 @@ void cfg_wizard(void)
 	free_msgs_cfg(&cfg);
 }
 
+void display_filename(BOOL force)
+{
+	static char last[MAX_PATH + 1];
+	const char* fname = cfg.filename;
+	if(strlen(fname) + 30 > uifc.scrn_width)
+		fname = getfname(fname);
+	if(force || strcmp(last, fname) != 0)
+		uifc.printf(29, 1, uifc.bclr|(uifc.cclr<<4), "%*s", uifc.scrn_width - 30, fname);
+	SAFECOPY(last, fname);
+}
+
 int main(int argc, char **argv)
 {
 	char	**mopt,*p;
     char    errormsg[MAX_PATH*2];
 	int 	i,j,main_dflt=0,chat_dflt=0;
-	char 	str[MAX_PATH+1];
+	char	cfg_fname[MAX_PATH + 1];
 	BOOL    door_mode=FALSE;
 	int		ciolib_mode=CIOLIB_MODE_AUTO;
 	char	compiler[32];
@@ -647,20 +659,21 @@ int main(int argc, char **argv)
 		if((mopt[i]=(char *)malloc(64))==NULL)
 			allocfail(64);
 
+	uifc.timedisplay = display_filename;
 	SAFEPRINTF2(title,"Synchronet for %s v%s",PLATFORM_DESC,VERSION);
 	if(uifc.scrn(title)) {
 		printf(" USCRN (len=%d) failed!\n",uifc.scrn_len+1);
 		bail(1);
 	}
 
-	SAFEPRINTF(str,"%smain.ini",cfg.ctrl_dir);
-	if(!fexist(str)) {
-		SAFEPRINTF(errormsg, "Main configuration file (%s) missing!",str);
+	SAFEPRINTF(cfg_fname, "%smain.ini", cfg.ctrl_dir);
+	if(!fexist(cfg_fname)) {
+		SAFEPRINTF(errormsg, "Main configuration file (%s) missing!", cfg_fname);
 		uifc.msg(errormsg);
 	}
-	FILE* fp = iniOpenFile(str, /* for_modify */TRUE);
+	FILE* fp = iniOpenFile(cfg_fname, /* for_modify */TRUE);
 	if(fp == NULL) {
-		SAFEPRINTF2(errormsg, "Error %d opening configuration file: %s", errno, str);
+		SAFEPRINTF2(errormsg, "Error %d opening configuration file: %s", errno, cfg_fname);
 		uifc.msg(errormsg);
 	} else {
 		cfg.new_install = iniReadBool(fp, ROOT_SECTION, "new_install", TRUE);
@@ -672,10 +685,10 @@ int main(int argc, char **argv)
 		if(run_wizard)
 			bail(0);
 	}
-
 	i=0;
 	strcpy(mopt[i++],"Nodes");
 	strcpy(mopt[i++],"System");
+	strcpy(mopt[i++],"Servers");
 	strcpy(mopt[i++],"Networks");
 	strcpy(mopt[i++],"File Areas");
 	strcpy(mopt[i++],"File Options");
@@ -689,6 +702,7 @@ int main(int argc, char **argv)
 	i = cryptInit();
 	(void)i;
 	while(1) {
+		*cfg.filename = '\0';
 		uifc.helpbuf=
 			"`Main Configuration Menu:`\n"
 			"\n"
@@ -697,6 +711,7 @@ int main(int argc, char **argv)
 			"\n"
 			"    `Nodes               ` Add, delete, or configure nodes\n"
 			"    `System              ` System-wide configuration options\n"
+			"    `Servers             ` TCP/IP Servers and Services\n"
 			"    `Networks            ` Networking configuration\n"
 			"    `File Areas          ` File area configuration\n"
 			"    `File Options        ` File area options\n"
@@ -721,12 +736,12 @@ int main(int argc, char **argv)
 				free_main_cfg(&cfg);
 				break;
 			case 1:
-				if(!load_main_cfg(&cfg, error, sizeof(error))) {
+				if(!load_xtrn_cfg(&cfg, error, sizeof(error))) {
 					SAFEPRINTF(errormsg,"ERROR: %s",error);
 					uifc.msg(errormsg);
 					break;
 				}
-				if(!load_xtrn_cfg(&cfg, error, sizeof(error))) {
+				if(!load_main_cfg(&cfg, error, sizeof(error))) {
 					SAFEPRINTF(errormsg,"ERROR: %s",error);
 					uifc.msg(errormsg);
 					break;
@@ -736,9 +751,12 @@ int main(int argc, char **argv)
 				free_main_cfg(&cfg);
 				break;
 			case 2:
-				net_cfg();
+				server_cfg();
 				break;
 			case 3:
+				net_cfg();
+				break;
+			case 4:
 				if(!load_main_cfg(&cfg, error, sizeof(error))) {
 					SAFEPRINTF(errormsg,"ERROR: %s",error);
 					uifc.msg(errormsg);
@@ -748,12 +766,12 @@ int main(int argc, char **argv)
 					SAFEPRINTF(errormsg,"ERROR: %s",error);
 					uifc.msg(errormsg);
 					break;
-				}	
+				}
 				xfer_cfg();
 				free_file_cfg(&cfg);
 				free_main_cfg(&cfg);
 				break;
-			case 4:
+			case 5:
 				if(!load_main_cfg(&cfg, error, sizeof(error))) {
 					SAFEPRINTF(errormsg,"ERROR: %s",error);
 					uifc.msg(errormsg);
@@ -768,12 +786,12 @@ int main(int argc, char **argv)
 				free_file_cfg(&cfg);
 				free_main_cfg(&cfg);
 				break;
-			case 5:
+			case 6:
 				if(!load_chat_cfg(&cfg, error, sizeof(error))) {
 					SAFEPRINTF(errormsg,"ERROR: %s",error);
 					uifc.msg(errormsg);
 					break;
-				}	
+				}
 				while(1) {
 					i=0;
 					strcpy(opt[i++],"Artificial Gurus");
@@ -809,12 +827,12 @@ int main(int argc, char **argv)
 							break;
 						case 3:
 							page_cfg();
-							break; 
-					} 
+							break;
+					}
 				}
 				free_chat_cfg(&cfg);
 				break;
-			case 6:
+			case 7:
 				if(!load_main_cfg(&cfg, error, sizeof(error))) {
 					SAFEPRINTF(errormsg, "ERROR: %s",error);
 					uifc.msg(errormsg);
@@ -829,7 +847,7 @@ int main(int argc, char **argv)
 				free_msgs_cfg(&cfg);
 				free_main_cfg(&cfg);
 				break;
-			case 7:
+			case 8:
 				if(!load_main_cfg(&cfg, error, sizeof(error))) {
 					SAFEPRINTF(errormsg, "ERROR: %s",error);
 					uifc.msg(errormsg);
@@ -844,7 +862,7 @@ int main(int argc, char **argv)
 				free_msgs_cfg(&cfg);
 				free_main_cfg(&cfg);
 				break;
-			case 8:
+			case 9:
 				if(!load_main_cfg(&cfg, error, sizeof(error))) {
 					SAFEPRINTF(errormsg, "ERROR: %s",error);
 					uifc.msg(errormsg);
@@ -853,7 +871,7 @@ int main(int argc, char **argv)
 				shell_cfg();
 				free_main_cfg(&cfg);
 				break;
-			case 9:
+			case 10:
 				if(!load_main_cfg(&cfg, error, sizeof(error))) {
 					SAFEPRINTF(errormsg, "ERROR: %s",error);
 					uifc.msg(errormsg);
@@ -868,7 +886,7 @@ int main(int argc, char **argv)
 				free_xtrn_cfg(&cfg);
 				free_main_cfg(&cfg);
 				break;
-			case 10:
+			case 11:
 				if(!load_main_cfg(&cfg, error, sizeof(error))) {
 					SAFEPRINTF(errormsg, "ERROR: %s",error);
 					uifc.msg(errormsg);
@@ -894,8 +912,8 @@ int main(int argc, char **argv)
 				i=uifc.list(WIN_MID|WIN_SAV,0,0,0,&i,0,"Exit SCFG",uifcYesNoOpts);
 				if(!i)
 					bail(0);
-				break; 
-		} 
+				break;
+		}
 	}
 }
 
@@ -1097,28 +1115,28 @@ void txt_cfg()
 				uifc.helpbuf=invalid_code;
 				uifc.msg(strInvalidCode);
 				uifc.helpbuf=0;
-				continue; 
+				continue;
 			}
 			if((cfg.txtsec=(txtsec_t **)realloc(cfg.txtsec
 				,sizeof(txtsec_t *)*(cfg.total_txtsecs+1)))==NULL) {
 				errormsg(WHERE,ERR_ALLOC,nulstr,cfg.total_txtsecs+1);
 				cfg.total_txtsecs=0;
 				bail(1);
-				continue; 
+				continue;
 			}
 			if(cfg.total_txtsecs)
 				for(u=cfg.total_txtsecs;u>i;u--)
 					cfg.txtsec[u]=cfg.txtsec[u-1];
 			if((cfg.txtsec[i]=(txtsec_t *)malloc(sizeof(txtsec_t)))==NULL) {
 				errormsg(WHERE,ERR_ALLOC,nulstr,sizeof(txtsec_t));
-				continue; 
+				continue;
 			}
 			memset((txtsec_t *)cfg.txtsec[i],0,sizeof(txtsec_t));
 			SAFECOPY(cfg.txtsec[i]->name,str);
 			SAFECOPY(cfg.txtsec[i]->code,code);
 			cfg.total_txtsecs++;
 			uifc.changes=1;
-			continue; 
+			continue;
 		}
 		if (msk == MSK_DEL || msk == MSK_CUT) {
 			if(msk == MSK_CUT)
@@ -1128,16 +1146,16 @@ void txt_cfg()
 			for(j=i;j<cfg.total_txtsecs;j++)
 				cfg.txtsec[j]=cfg.txtsec[j+1];
 			uifc.changes=1;
-			continue; 
+			continue;
 		}
 		if (msk == MSK_COPY) {
 			savtxtsec=*cfg.txtsec[i];
-			continue; 
+			continue;
 		}
 		if (msk == MSK_PASTE) {
 			*cfg.txtsec[i]=savtxtsec;
 			uifc.changes=1;
-			continue; 
+			continue;
 		}
 		if (msk != 0)
 			continue;
@@ -1197,15 +1215,15 @@ void txt_cfg()
 					else {
 						uifc.helpbuf=invalid_code;
 						uifc.msg(strInvalidCode);
-						uifc.helpbuf=0; 
+						uifc.helpbuf=0;
 					}
-					break; 
+					break;
 				case 2:
 					sprintf(str,"%s Text Section",cfg.txtsec[i]->name);
 					getar(str,cfg.txtsec[i]->arstr);
 					break;
-			} 
-		} 
+			}
+		}
 	}
 }
 
@@ -1285,28 +1303,28 @@ void shell_cfg()
 				uifc.helpbuf=invalid_code;
 				uifc.msg(strInvalidCode);
 				uifc.helpbuf=0;
-				continue; 
+				continue;
 			}
 			if((cfg.shell=(shell_t **)realloc(cfg.shell
 				,sizeof(shell_t *)*(cfg.total_shells+1)))==NULL) {
 				errormsg(WHERE,ERR_ALLOC,nulstr,cfg.total_shells+1);
 				cfg.total_shells=0;
 				bail(1);
-				continue; 
+				continue;
 			}
 			if(cfg.total_shells)
 				for(u=cfg.total_shells;u>i;u--)
 					cfg.shell[u]=cfg.shell[u-1];
 			if((cfg.shell[i]=(shell_t *)malloc(sizeof(shell_t)))==NULL) {
 				errormsg(WHERE,ERR_ALLOC,nulstr,sizeof(shell_t));
-				continue; 
+				continue;
 			}
 			memset((shell_t *)cfg.shell[i],0,sizeof(shell_t));
 			SAFECOPY(cfg.shell[i]->name,str);
 			SAFECOPY(cfg.shell[i]->code,code);
 			cfg.total_shells++;
 			uifc.changes=1;
-			continue; 
+			continue;
 		}
 		if (msk == MSK_DEL || msk == MSK_CUT) {
 			if(msk == MSK_CUT)
@@ -1316,16 +1334,16 @@ void shell_cfg()
 			for(j=i;j<cfg.total_shells;j++)
 				cfg.shell[j]=cfg.shell[j+1];
 			uifc.changes=1;
-			continue; 
+			continue;
 		}
 		if (msk == MSK_COPY) {
 			savshell=*cfg.shell[i];
-			continue; 
+			continue;
 		}
 		if (msk == MSK_PASTE) {
 			*cfg.shell[i]=savshell;
 			uifc.changes=1;
-			continue; 
+			continue;
 		}
 		if (msk != 0)
 			continue;
@@ -1406,15 +1424,15 @@ void shell_cfg()
 					else {
 						uifc.helpbuf=invalid_code;
 						uifc.msg(strInvalidCode);
-						uifc.helpbuf=0; 
+						uifc.helpbuf=0;
 					}
 					break;
 				case 2:
 					SAFEPRINTF(str,"%s Command Shell",cfg.shell[i]->name);
 					getar(str,cfg.shell[i]->arstr);
-					break; 
-			} 
-		} 
+					break;
+			}
+		}
 	}
 }
 
@@ -1484,33 +1502,33 @@ void getar(char *desc, char *inar)
 		for(i=0;i<n;i++) {					/* Shorten operators */
 			if(!strncmp(ar+i,"AND",3)) {
 				strcat(str,"&");
-				i+=2; 
+				i+=2;
 			}
 			else if(!strncmp(ar+i,"NOT",3)) {
 				strcat(str,"!");
-				i+=2; 
+				i+=2;
 			}
 			else if(!strncmp(ar+i,"EQUAL",5)) {
 				strcat(str,"=");
-				i+=4; 
+				i+=4;
 			}
 			else if(!strncmp(ar+i,"EQUALS",6)) {
 				strcat(str,"=");
-				i+=5; 
+				i+=5;
 			}
 			else if(!strncmp(ar+i,"EQUAL TO",8)) {
 				strcat(str,"=");
-				i+=7; 
+				i+=7;
 			}
 			else if(!strncmp(ar+i,"OR",2)) {
 				strcat(str,"|");
-				i+=1; 
+				i+=1;
 			}
 			else
-				strncat(str,ar+i,1); 
+				strncat(str,ar+i,1);
 		}
 		SAFECOPY(ar,str);
-		len=strlen(ar); 
+		len=strlen(ar);
 	}
 
 	if(len>=30) {
@@ -1519,21 +1537,21 @@ void getar(char *desc, char *inar)
 		for(i=0;i<n;i++) {					/* Remove spaces from ! and = */
 			if(!strncmp(ar+i," ! ",3)) {
 				strcat(str,"!");
-				i+=2; 
+				i+=2;
 			}
 			else if(!strncmp(ar+i,"= ",2)) {
 				strcat(str,"=");
-                i++; 
+                i++;
 			}
 			else if(!strncmp(ar+i," = ",3)) {
 				strcat(str,"=");
-				i+=2; 
+				i+=2;
 			}
 			else
-				strncat(str,ar+i,1); 
+				strncat(str,ar+i,1);
 		}
 		SAFECOPY(ar,str);
-        len=strlen(ar); 
+        len=strlen(ar);
 	}
 
 	if(len>=30) {
@@ -1542,17 +1560,17 @@ void getar(char *desc, char *inar)
 		for(i=0;i<n;i++) {					/* Remove spaces from & and | */
 			if(!strncmp(ar+i," & ",3)) {
 				strcat(str," ");
-				i+=2; 
+				i+=2;
 			}
 			else if(!strncmp(ar+i," | ",3)) {
 				strcat(str,"|");
-				i+=2; 
+				i+=2;
 			}
 			else
-				strncat(str,ar+i,1); 
+				strncat(str,ar+i,1);
 		}
 		SAFECOPY(ar,str);
-        len=strlen(ar); 
+        len=strlen(ar);
 	}
 
 	if(len>=30) {					/* change week days to numbers */
@@ -1566,10 +1584,10 @@ void getar(char *desc, char *inar)
 					break;
 				}
 			if(j==7)
-				strncat(str,ar+i,1); 
+				strncat(str,ar+i,1);
 		}
         SAFECOPY(ar,str);
-        len=strlen(ar); 
+        len=strlen(ar);
 	}
 
 	if(len>=30) {				  /* Shorten parameters */
@@ -1578,112 +1596,112 @@ void getar(char *desc, char *inar)
 		for(i=0;i<n;i++) {
 			if(!strncmp(ar+i,"AGE",3)) {
 				strcat(str,"$A");
-				i+=2; 
+				i+=2;
 			}
 			else if(!strncmp(ar+i,"BPS",3)) {
 				strcat(str,"$B");
-				i+=2; 
+				i+=2;
 			}
 			else if(!strncmp(ar+i,"PCR",3)) {
 				strcat(str,"$P");
-				i+=2; 
+				i+=2;
 			}
 			else if(!strncmp(ar+i,"RIP",3)) {
 				strcat(str,"$*");
-				i+=2; 
+				i+=2;
 			}
 			else if(!strncmp(ar+i,"SEX",3)) {
 				strcat(str,"$S");
-				i+=2; 
+				i+=2;
 			}
 			else if(!strncmp(ar+i,"UDR",3)) {
 				strcat(str,"$K");
-				i+=2; 
+				i+=2;
 			}
 			else if(!strncmp(ar+i,"DAY",3)) {
 				strcat(str,"$W");
-                i+=2; 
+                i+=2;
 			}
 			else if(!strncmp(ar+i,"ANSI",4)) {
 				strcat(str,"$[");
-                i+=3; 
+                i+=3;
 			}
 			else if(!strncmp(ar+i,"UDFR",4)) {
 				strcat(str,"$D");
-				i+=3; 
+				i+=3;
 			}
 			else if(!strncmp(ar+i,"FLAG",4)) {
 				strcat(str,"$F");
-				i+=3; 
+				i+=3;
 			}
 			else if(!strncmp(ar+i,"NODE",4)) {
 				strcat(str,"$N");
-				i+=3; 
+				i+=3;
 			}
 			else if(!strncmp(ar+i,"NULL",4)) {
 				strcat(str,"$0");
-                i+=3; 
+                i+=3;
 			}
 			else if(!strncmp(ar+i,"TIME",4)) {
 				strcat(str,"$T");
-				i+=3; 
+				i+=3;
 			}
 			else if(!strncmp(ar+i,"USER",4)) {
 				strcat(str,"$U");
-				i+=3; 
+				i+=3;
 			}
 			else if(!strncmp(ar+i,"REST",4)) {
 				strcat(str,"$Z");
-                i+=3; 
+                i+=3;
 			}
 			else if(!strncmp(ar+i,"LOCAL",5)) {
 				strcat(str,"$G");
-				i+=4; 
+				i+=4;
 			}
 			else if(!strncmp(ar+i,"LEVEL",5)) {
 				strcat(str,"$L");
-                i+=4; 
+                i+=4;
 			}
 			else if(!strncmp(ar+i,"TLEFT",5)) {
 				strcat(str,"$R");
-				i+=4; 
+				i+=4;
 			}
 			else if(!strncmp(ar+i,"TUSED",5)) {
 				strcat(str,"$O");
-				i+=4; 
+				i+=4;
 			}
 			else if(!strncmp(ar+i,"EXPIRE",6)) {
 				strcat(str,"$E");
-				i+=5; 
+				i+=5;
 			}
 			else if(!strncmp(ar+i,"CREDIT",6)) {
 				strcat(str,"$C");
-                i+=5; 
+                i+=5;
 			}
 			else if(!strncmp(ar+i,"EXEMPT",6)) {
 				strcat(str,"$X");
-                i+=5; 
+                i+=5;
 			}
 			else if(!strncmp(ar+i,"RANDOM",6)) {
 				strcat(str,"$Q");
-                i+=5; 
+                i+=5;
 			}
 			else if(!strncmp(ar+i,"LASTON",6)) {
 				strcat(str,"$Y");
-                i+=5; 
+                i+=5;
 			}
 			else if(!strncmp(ar+i,"LOGONS",6)) {
 				strcat(str,"$V");
-                i+=5; 
+                i+=5;
 			}
 			else if(!strncmp(ar+i,":00",3)) {
-				i+=2; 
+				i+=2;
 			}
 			else
-				strncat(str,ar+i,1); 
+				strncat(str,ar+i,1);
 }
 		SAFECOPY(ar,str);
-		len=strlen(ar); 
+		len=strlen(ar);
 }
 	if(len>=30) {				  /* Remove all spaces and &s */
 		str[0]=0;
@@ -1759,13 +1777,13 @@ void getar(char *desc, char *inar)
 			i=uifc.list(WIN_MID|WIN_SAV,0,0,0,&i,0,"Are You Sure",uifcYesNoOpts);
 			if(!i) {
 				ar[0]=0;
-				uifc.changes=1; 
+				uifc.changes=1;
 			}
 			break;
 		case 2:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=whichlogic();
 			if(i==-1)
@@ -1787,7 +1805,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-					strcat(ar," OR "); 
+					strcat(ar," OR ");
 			}
 			strcat(ar,"LEVEL ");
 			switch(i) {
@@ -1799,14 +1817,14 @@ void getar(char *desc, char *inar)
 					break;
 				case 3:
 					strcat(ar,"NOT ");
-					break; 
+					break;
 			}
 			strcat(ar,str);
 			break;
 		case 3:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 
 			for(i=0;i<4;i++)
@@ -1834,7 +1852,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"FLAG ");
 			if(i)
@@ -1844,7 +1862,7 @@ void getar(char *desc, char *inar)
 		case 4:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=whichlogic();
 			if(i==-1)
@@ -1866,7 +1884,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"AGE ");
 			switch(i) {
@@ -1878,14 +1896,14 @@ void getar(char *desc, char *inar)
 					break;
 				case 3:
 					strcat(ar,"NOT ");
-					break; 
+					break;
 			}
 			strcat(ar,str);
             break;
 		case 5:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			str[0]=0;
 			uifc.helpbuf=
@@ -1906,7 +1924,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-					strcat(ar," OR "); 
+					strcat(ar," OR ");
 			}
 			strcat(ar,"SEX ");
 			strcat(ar,str);
@@ -1914,7 +1932,7 @@ void getar(char *desc, char *inar)
 		case 6:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=whichlogic();
 			if(i==-1)
@@ -1932,7 +1950,7 @@ void getar(char *desc, char *inar)
 			j=atoi(str);
 			if(j>=300 && j<30000) {
 				j/=100;
-				sprintf(str,"%d",j); 
+				sprintf(str,"%d",j);
 			}
 			if(ar[0]) {
 				j=whichcond();
@@ -1941,7 +1959,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"BPS ");
 			switch(i) {
@@ -1953,14 +1971,14 @@ void getar(char *desc, char *inar)
 					break;
 				case 3:
 					strcat(ar,"NOT ");
-					break; 
+					break;
 			}
 			strcat(ar,str);
             break;
 		case 7:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=whichlogic();
 			if(i==-1)
@@ -1983,7 +2001,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"PCR ");
 			switch(i) {
@@ -1995,14 +2013,14 @@ void getar(char *desc, char *inar)
 					break;
 				case 3:
 					strcat(ar,"NOT ");
-					break; 
+					break;
 			}
 			strcat(ar,str);
             break;
 		case 8:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=whichlogic();
 			if(i==-1)
@@ -2025,7 +2043,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"CREDIT ");
 			switch(i) {
@@ -2037,14 +2055,14 @@ void getar(char *desc, char *inar)
 					break;
 				case 3:
 					strcat(ar,"NOT ");
-					break; 
+					break;
 			}
 			strcat(ar,str);
             break;
 		case 9:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=whichlogic();
 			if(i==-1)
@@ -2069,7 +2087,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"UDR ");
 			switch(i) {
@@ -2081,14 +2099,14 @@ void getar(char *desc, char *inar)
 					break;
 				case 3:
 					strcat(ar,"NOT ");
-					break; 
+					break;
 			}
 			strcat(ar,str);
             break;
 		case 10:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=whichlogic();
 			if(i==-1)
@@ -2114,7 +2132,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"UDFR ");
 			switch(i) {
@@ -2126,14 +2144,14 @@ void getar(char *desc, char *inar)
 					break;
 				case 3:
 					strcat(ar,"NOT ");
-					break; 
+					break;
 			}
 			strcat(ar,str);
             break;
 		case 11:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=0;
 			strcpy(opt[0],"Before");
@@ -2160,7 +2178,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"TIME ");
 			if(!i)
@@ -2170,7 +2188,7 @@ void getar(char *desc, char *inar)
 		case 12:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=whichlogic();
 			if(i==-1)
@@ -2197,7 +2215,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"DAY ");
 			switch(i) {
@@ -2209,14 +2227,14 @@ void getar(char *desc, char *inar)
 					break;
 				case 3:
 					strcat(ar,"NOT ");
-					break; 
+					break;
 			}
 			strcat(ar,str);
             break;
 		case 13:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=whichlogic();
 			if(i==-1)
@@ -2238,7 +2256,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"NODE ");
 			switch(i) {
@@ -2250,14 +2268,14 @@ void getar(char *desc, char *inar)
 					break;
 				case 3:
 					strcat(ar,"NOT ");
-					break; 
+					break;
 			}
 			strcat(ar,str);
             break;
 		case 14:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=whichlogic();
 			if(i==-1)
@@ -2279,7 +2297,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"USER ");
 			switch(i) {
@@ -2291,7 +2309,7 @@ void getar(char *desc, char *inar)
 					break;
 				case 3:
 					strcat(ar,"NOT ");
-					break; 
+					break;
 			}
 			strcat(ar,str);
             break;
@@ -2299,7 +2317,7 @@ void getar(char *desc, char *inar)
 		case 15:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=whichlogic();
 			if(i==-1)
@@ -2322,7 +2340,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"TLEFT ");
 			switch(i) {
@@ -2334,7 +2352,7 @@ void getar(char *desc, char *inar)
 					break;
 				case 3:
 					strcat(ar,"NOT ");
-					break; 
+					break;
 			}
 			strcat(ar,str);
 			break;
@@ -2342,7 +2360,7 @@ void getar(char *desc, char *inar)
 		case 16:
 			if(strlen(ar)>=30) {
 				uifc.msg("Maximum string length reached");
-                break; 
+                break;
 			}
 			i=whichlogic();
 			if(i==-1)
@@ -2365,7 +2383,7 @@ void getar(char *desc, char *inar)
 				if(!j)
 					strcat(ar," AND ");
 				else
-                    strcat(ar," OR "); 
+                    strcat(ar," OR ");
 			}
 			strcat(ar,"EXPIRE ");
 			switch(i) {
@@ -2377,12 +2395,12 @@ void getar(char *desc, char *inar)
 					break;
 				case 3:
 					strcat(ar,"NOT ");
-					break; 
+					break;
 			}
 			strcat(ar,str);
             break;
-			
-		} 
+
+		}
 	}
 	sprintf(inar,"%.*s",LEN_ARSTR,ar);
 }
@@ -2434,6 +2452,16 @@ void bail(int code)
 		(void)getchar();
 	}
     else if(forcesave) {
+		FILE* fp;
+		BOOL run_bbs, run_ftp, run_web, run_mail, run_services;
+		global_startup_t global_startup = {0};
+		bbs_startup_t bbs_startup = {0};
+		web_startup_t web_startup = {0};
+		ftp_startup_t ftp_startup = {0};
+		mail_startup_t mail_startup = {0};
+		services_startup_t services_startup = {0};
+
+		uifc.pop("Force-saving configuration files...");
         load_main_cfg(&cfg, error, sizeof(error));
         load_msgs_cfg(&cfg, error, sizeof(error));
         load_file_cfg(&cfg, error, sizeof(error));
@@ -2444,7 +2472,47 @@ void bail(int code)
         save_msgs_cfg(&cfg,backup_level);
         save_file_cfg(&cfg,backup_level);
         save_chat_cfg(&cfg,backup_level);
-        save_xtrn_cfg(&cfg,backup_level); 
+		save_xtrn_cfg(&cfg,backup_level);
+
+		sbbs_get_ini_fname(cfg.filename, cfg.ctrl_dir);
+
+		fp = iniOpenFile(cfg.filename, /* for_modify? */false);
+		if(fp == NULL)
+			uifc.msgf("Error opening %s", cfg.filename);
+		else {
+			sbbs_read_ini(
+				 fp
+				,cfg.filename
+				,&global_startup
+				,&run_bbs
+				,&bbs_startup
+				,&run_ftp
+				,&ftp_startup
+				,&run_web
+				,&web_startup
+				,&run_mail
+				,&mail_startup
+				,&run_services
+				,&services_startup
+				);
+			sbbs_write_ini(
+				 fp
+				,&cfg
+				,&global_startup
+				,run_bbs
+				,&bbs_startup
+				,run_ftp
+				,&ftp_startup
+				,run_web
+				,&web_startup
+				,run_mail
+				,&mail_startup
+				,run_services
+				,&services_startup
+				);
+			iniCloseFile(fp);
+		}
+        uifc.pop(NULL);
 	}
 
 	uifc.pop("Exiting");
diff --git a/src/sbbs3/scfg/scfg.h b/src/sbbs3/scfg/scfg.h
index b75b8bc2d94dc8a3efcdb4637bc4c1f6fe04a334..ef6ba97c152ecc09873ed70f5e0c8d1797439881 100644
--- a/src/sbbs3/scfg/scfg.h
+++ b/src/sbbs3/scfg/scfg.h
@@ -146,6 +146,7 @@ void init_mdms(void);
 void guru_cfg(void);
 void actsets_cfg(void);
 void chan_cfg(void);
+void server_cfg(void);
 void mdm_cfg(int mdmnum);
 void wizard_msg(int page, int total, const char* text);
 int edit_sys_name(int page, int total);
@@ -185,7 +186,7 @@ BOOL save_main_cfg(scfg_t*, int);
 BOOL save_node_cfg(scfg_t*, int);
 BOOL save_msgs_cfg(scfg_t*, int);
 BOOL save_file_cfg(scfg_t*, int);
-BOOL save_chat_cfg(scfg_t*, int);	
+BOOL save_chat_cfg(scfg_t*, int);
 BOOL save_xtrn_cfg(scfg_t*, int);
 
 long import_msg_areas(enum import_list_type, FILE*, unsigned grpnum, int min_confnum, int max_confnum
diff --git a/src/sbbs3/scfg/scfg.vcxproj b/src/sbbs3/scfg/scfg.vcxproj
index 90c77a2919c3f9f03fc1958526a0a2d7ee05f92f..ef57ffbe89b6714213e9021ffe1b280a498cac98 100644
--- a/src/sbbs3/scfg/scfg.vcxproj
+++ b/src/sbbs3/scfg/scfg.vcxproj
@@ -166,6 +166,7 @@
     </ClCompile>
     <ClCompile Include="..\getstats.c" />
     <ClCompile Include="..\msgdate.c" />
+    <ClCompile Include="..\sbbs_ini.c" />
     <ClCompile Include="..\userdat.c" />
     <ClCompile Include="scfg.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
@@ -203,6 +204,7 @@
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
+    <ClCompile Include="scfgsrvr.c" />
     <ClCompile Include="scfgsub.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
diff --git a/src/sbbs3/scfg/scfgnet.c b/src/sbbs3/scfg/scfgnet.c
index 31bba2ec8b9f84cb961f33df4fad5ff4d99f37ed..b40f1f7697d6e55666e18571fea41ce12185644b 100644
--- a/src/sbbs3/scfg/scfgnet.c
+++ b/src/sbbs3/scfg/scfgnet.c
@@ -62,7 +62,7 @@ bool new_qhub_sub(qhub_t* qhub, unsigned subnum, sub_t* sub, unsigned confnum)
 	for(unsigned u = qhub->subs; u > subnum; u--) {
 		qhub->sub[u]=qhub->sub[u-1];
 		qhub->conf[u]=qhub->conf[u-1];
-		qhub->mode[u]=qhub->mode[u-1]; 
+		qhub->mode[u]=qhub->mode[u-1];
 	}
 	qhub->sub[subnum] = sub;
 	qhub->conf[subnum] = confnum;
@@ -82,14 +82,14 @@ faddr_t atofaddr(char *str)
 	addr.zone=addr.net=addr.node=addr.point=0;
 	if((p=strchr(str,':'))!=NULL) {
 		addr.zone=atoi(str);
-		addr.net=atoi(p+1); 
+		addr.net=atoi(p+1);
 	}
 	else {
 		if(cfg.total_faddrs)
 			addr.zone=cfg.faddr[0].zone;
 		else
 			addr.zone=1;
-		addr.net=atoi(str); 
+		addr.net=atoi(str);
 	}
 	if(!addr.zone)              /* no such thing as zone 0 */
 		addr.zone=1;
@@ -100,7 +100,7 @@ faddr_t atofaddr(char *str)
 			addr.net=cfg.faddr[0].net;
 		else
 			addr.net=1;
-		addr.node=atoi(str); 
+		addr.node=atoi(str);
 	}
 	if((p=strchr(str,'.'))!=NULL)
 		addr.point=atoi(p+1);
@@ -132,7 +132,7 @@ uint getsub(void)
 		for(j=k=0;j<cfg.total_subs && k<MAX_OPTS;j++)
 			if(cfg.sub[j]->grp==i) {
 				sprintf(opt[k],"%-25s",cfg.sub[j]->lname);
-				subnum[k++]=j; 
+				subnum[k++]=j;
 			}
 		opt[k][0]=0;
 		sprintf(str,"Select %s Sub-board",cfg.grp[i]->sname);
@@ -141,7 +141,7 @@ uint getsub(void)
 			continue;
 		sub_dflt++;
 		sub_bar++;
-		return(subnum[j]); 
+		return(subnum[j]);
 	}
 }
 
@@ -388,6 +388,7 @@ void net_cfg()
 	int		mode;
 
 	while(1) {
+		*cfg.filename = '\0';
 		i=0;
 		strcpy(opt[i++],"Internet E-mail");
 		strcpy(opt[i++],"QWK Packet Networks");
@@ -498,7 +499,7 @@ void net_cfg()
 								cfg.qhub[i]->node = NODE_ANY;
 								cfg.qhub[i]->days=0x7f; /* all days */
 								uifc.changes=1;
-								continue; 
+								continue;
 							}
 							if (msk == MSK_DEL) {
 								free(cfg.qhub[i]->mode);
@@ -508,16 +509,16 @@ void net_cfg()
 								cfg.total_qhubs--;
 								while(i<cfg.total_qhubs) {
 									cfg.qhub[i]=cfg.qhub[i+1];
-									i++; 
+									i++;
 								}
 								uifc.changes=1;
-								continue; 
+								continue;
 							}
-							qhub_edit(i); 
+							qhub_edit(i);
 						}
-						break; 
-				} 
-			} 
+						break;
+				}
+			}
 		}
 
 		else if(i==2) { 	/* FidoNet Stuff */
@@ -601,7 +602,7 @@ void net_cfg()
 								else
 									sprintf(str,"AKA %u",i);
 								sprintf(opt[i],"%-8.8s %16s"
-									,str,smb_faddrtoa(&cfg.faddr[i],tmp)); 
+									,str,smb_faddrtoa(&cfg.faddr[i],tmp));
 							}
 							opt[i][0]=0;
 							mode=WIN_RHT|WIN_SAV|WIN_ACT|WIN_INSACT;
@@ -633,7 +634,7 @@ void net_cfg()
 										,sizeof(faddr_t)*cfg.total_faddrs+1);
 									cfg.total_faddrs=0;
 									bail(1);
-									continue; 
+									continue;
 								}
 
 								for(j=cfg.total_faddrs;j>i;j--)
@@ -642,7 +643,7 @@ void net_cfg()
 								cfg.faddr[i]=newfaddr;
 								cfg.total_faddrs++;
 								uifc.changes=1;
-								continue; 
+								continue;
 							}
 							if (msk == MSK_COPY) {
 								savfaddr = cfg.faddr[i];
@@ -654,15 +655,15 @@ void net_cfg()
 								cfg.total_faddrs--;
 								while(i<cfg.total_faddrs) {
 									cfg.faddr[i]=cfg.faddr[i+1];
-									i++; 
+									i++;
 								}
 								uifc.changes=1;
-								continue; 
+								continue;
 							}
 							smb_faddrtoa(&cfg.faddr[i],str);
 							if(uifc.input(WIN_MID|WIN_SAV,0,0,"Address"
 								,str,25,K_EDIT|K_UPPER) >= 1)
-								cfg.faddr[i]=atofaddr(str); 
+								cfg.faddr[i]=atofaddr(str);
 						}
 						break;
 					case 1:
@@ -726,11 +727,11 @@ void net_cfg()
 							,"Allow Users to Send NetMail",uifcYesNoOpts);
 						if(!i && !(cfg.netmail_misc&NMAIL_ALLOW)) {
 							uifc.changes=1;
-							cfg.netmail_misc|=NMAIL_ALLOW; 
+							cfg.netmail_misc|=NMAIL_ALLOW;
 						}
 						else if(i==1 && cfg.netmail_misc&NMAIL_ALLOW) {
 							uifc.changes=1;
-							cfg.netmail_misc&=~NMAIL_ALLOW; 
+							cfg.netmail_misc&=~NMAIL_ALLOW;
 						}
 						break;
 					case 6:
@@ -745,11 +746,11 @@ void net_cfg()
 							,"Allow Users to Send NetMail File Attachments",uifcYesNoOpts);
 						if(!i && !(cfg.netmail_misc&NMAIL_FILE)) {
 							uifc.changes=1;
-							cfg.netmail_misc|=NMAIL_FILE; 
+							cfg.netmail_misc|=NMAIL_FILE;
 						}
 						else if(i==1 && cfg.netmail_misc&NMAIL_FILE) {
 							uifc.changes=1;
-							cfg.netmail_misc&=~NMAIL_FILE; 
+							cfg.netmail_misc&=~NMAIL_FILE;
 						}
 						break;
 					case 7:
@@ -769,11 +770,11 @@ void net_cfg()
 							,"Use Aliases in NetMail",uifcYesNoOpts);
 						if(!i && !(cfg.netmail_misc&NMAIL_ALIAS)) {
 							uifc.changes=1;
-							cfg.netmail_misc|=NMAIL_ALIAS; 
+							cfg.netmail_misc|=NMAIL_ALIAS;
 						}
 						else if(i==1 && cfg.netmail_misc&NMAIL_ALIAS) {
 							uifc.changes=1;
-							cfg.netmail_misc&=~NMAIL_ALIAS; 
+							cfg.netmail_misc&=~NMAIL_ALIAS;
 						}
 						break;
 					case 8:
@@ -788,11 +789,11 @@ void net_cfg()
 							,"NetMail Defaults to Crash Status",uifcYesNoOpts);
 						if(!i && !(cfg.netmail_misc&NMAIL_CRASH)) {
 							uifc.changes=1;
-							cfg.netmail_misc|=NMAIL_CRASH; 
+							cfg.netmail_misc|=NMAIL_CRASH;
 						}
 						else if(i==1 && cfg.netmail_misc&NMAIL_CRASH) {
 							uifc.changes=1;
-							cfg.netmail_misc&=~NMAIL_CRASH; 
+							cfg.netmail_misc&=~NMAIL_CRASH;
 						}
 						break;
 					case 9:
@@ -807,11 +808,11 @@ void net_cfg()
 							,"NetMail Defaults to Direct Status",uifcYesNoOpts);
 						if(!i && !(cfg.netmail_misc&NMAIL_DIRECT)) {
 							uifc.changes=1;
-							cfg.netmail_misc|=NMAIL_DIRECT; 
+							cfg.netmail_misc|=NMAIL_DIRECT;
 						}
 						else if(i==1 && cfg.netmail_misc&NMAIL_DIRECT) {
 							uifc.changes=1;
-							cfg.netmail_misc&=~NMAIL_DIRECT; 
+							cfg.netmail_misc&=~NMAIL_DIRECT;
 						}
 						break;
 					case 10:
@@ -826,11 +827,11 @@ void net_cfg()
 							,"NetMail Defaults to Hold Status",uifcYesNoOpts);
 						if(!i && !(cfg.netmail_misc&NMAIL_HOLD)) {
 							uifc.changes=1;
-							cfg.netmail_misc|=NMAIL_HOLD; 
+							cfg.netmail_misc|=NMAIL_HOLD;
 						}
 						else if(i==1 && cfg.netmail_misc&NMAIL_HOLD) {
 							uifc.changes=1;
-							cfg.netmail_misc&=~NMAIL_HOLD; 
+							cfg.netmail_misc&=~NMAIL_HOLD;
 						}
 						break;
 					case 11:
@@ -845,11 +846,11 @@ void net_cfg()
 							,"Kill NetMail After it is Sent",uifcYesNoOpts);
 						if(!i && !(cfg.netmail_misc&NMAIL_KILL)) {
 							uifc.changes=1;
-							cfg.netmail_misc|=NMAIL_KILL; 
+							cfg.netmail_misc|=NMAIL_KILL;
 						}
 						else if(i==1 && cfg.netmail_misc&NMAIL_KILL) {
 							uifc.changes=1;
-							cfg.netmail_misc&=~NMAIL_KILL; 
+							cfg.netmail_misc&=~NMAIL_KILL;
 						}
 						break;
 					case 12:
@@ -864,7 +865,7 @@ void net_cfg()
 							,"Cost in Credits to Send NetMail"
 							,str,10,K_EDIT|K_NUMBER);
 						cfg.netmail_cost=atol(str);
-						break; 
+						break;
 					case 13:
 						i = (cfg.netmail_misc & NMAIL_CHSRCADDR) ? 0 : 1;
 						uifc.helpbuf=
@@ -878,15 +879,15 @@ void net_cfg()
 							,"Allow Senders of NetMail to Choose the Source Address",uifcYesNoOpts);
 						if(!i && !(cfg.netmail_misc&NMAIL_CHSRCADDR)) {
 							uifc.changes=1;
-							cfg.netmail_misc|=NMAIL_CHSRCADDR; 
+							cfg.netmail_misc|=NMAIL_CHSRCADDR;
 						}
 						else if(i==1 && cfg.netmail_misc&NMAIL_CHSRCADDR) {
 							uifc.changes=1;
-							cfg.netmail_misc&=~NMAIL_CHSRCADDR; 
+							cfg.netmail_misc&=~NMAIL_CHSRCADDR;
 						}
 						break;
-				} 
-			} 
+				}
+			}
 		}
 		else if(i==0) { 	/* Internet E-mail */
 			done=0;
@@ -968,11 +969,11 @@ void net_cfg()
 							,"Allow Users to Send E-mail",uifcYesNoOpts);
 						if(!i && !(cfg.inetmail_misc&NMAIL_ALLOW)) {
 							uifc.changes=1;
-							cfg.inetmail_misc|=NMAIL_ALLOW; 
+							cfg.inetmail_misc|=NMAIL_ALLOW;
 						}
 						else if(i==1 && cfg.inetmail_misc&NMAIL_ALLOW) {
 							uifc.changes=1;
-							cfg.inetmail_misc&=~NMAIL_ALLOW; 
+							cfg.inetmail_misc&=~NMAIL_ALLOW;
 						}
 						break;
 					case 4:
@@ -987,11 +988,11 @@ void net_cfg()
 							,"Allow Users to Send E-mail with File Attachments",uifcYesNoOpts);
 						if(!i && !(cfg.inetmail_misc&NMAIL_FILE)) {
 							uifc.changes=1;
-							cfg.inetmail_misc|=NMAIL_FILE; 
+							cfg.inetmail_misc|=NMAIL_FILE;
 						}
 						else if(i==1 && cfg.inetmail_misc&NMAIL_FILE) {
 							uifc.changes=1;
-							cfg.inetmail_misc&=~NMAIL_FILE; 
+							cfg.inetmail_misc&=~NMAIL_FILE;
 						}
 						break;
 					case 5:
@@ -1011,11 +1012,11 @@ void net_cfg()
 							,"Use Aliases in Internet E-mail",uifcYesNoOpts);
 						if(!i && !(cfg.inetmail_misc&NMAIL_ALIAS)) {
 							uifc.changes=1;
-							cfg.inetmail_misc|=NMAIL_ALIAS; 
+							cfg.inetmail_misc|=NMAIL_ALIAS;
 						}
 						else if(i==1 && cfg.inetmail_misc&NMAIL_ALIAS) {
 							uifc.changes=1;
-							cfg.inetmail_misc&=~NMAIL_ALIAS; 
+							cfg.inetmail_misc&=~NMAIL_ALIAS;
 						}
 						break;
 					case 6:
@@ -1030,7 +1031,7 @@ void net_cfg()
 							,"Kill Internet E-mail After it is Sent",uifcYesNoOpts);
 						if(!i && !(cfg.inetmail_misc&NMAIL_KILL)) {
 							uifc.changes=1;
-							cfg.inetmail_misc|=NMAIL_KILL; 
+							cfg.inetmail_misc|=NMAIL_KILL;
 						}
 						else if(i==1 && cfg.inetmail_misc&NMAIL_KILL) {
 							uifc.changes=1;
@@ -1050,9 +1051,9 @@ void net_cfg()
 							,"Cost in Credits to Send Internet E-mail"
 							,str,10,K_EDIT|K_NUMBER);
 						cfg.inetmail_cost=atol(str);
-						break; 
-				} 
-			} 
+						break;
+				}
+			}
 		}
 
 		i=save_changes(WIN_MID|WIN_SAV);
@@ -1139,11 +1140,11 @@ void qhub_edit(int num)
 		sprintf(opt[i++],"%-27.27s%s","Call-out Days",daystr(cfg.qhub[num]->days));
 		if(cfg.qhub[num]->freq) {
 			sprintf(str,"%u times a day",1440/cfg.qhub[num]->freq);
-			sprintf(opt[i++],"%-27.27s%s","Call-out Frequency",str); 
+			sprintf(opt[i++],"%-27.27s%s","Call-out Frequency",str);
 		}
 		else {
 			sprintf(str,"%2.2u:%2.2u",cfg.qhub[num]->time/60,cfg.qhub[num]->time%60);
-			sprintf(opt[i++],"%-27.27s%s","Call-out Time",str); 
+			sprintf(opt[i++],"%-27.27s%s","Call-out Time",str);
 		}
 		sprintf(opt[i++],"%-27.27s%s","Include Kludge Lines", cfg.qhub[num]->misc&QHUB_NOKLUDGES ? "No":"Yes");
 		sprintf(opt[i++],"%-27.27s%s","Include VOTING.DAT File", cfg.qhub[num]->misc&QHUB_NOVOTING ? "No":"Yes");
@@ -1302,7 +1303,7 @@ void qhub_edit(int num)
 					if(i==-1)
 						break;
 					cfg.qhub[num]->days^=(1<<i);
-					uifc.changes=1; 
+					uifc.changes=1;
 				}
 				break;
 			case __COUNTER__:
@@ -1332,8 +1333,8 @@ void qhub_edit(int num)
 						cfg.qhub[num]->freq=0;
 						cfg.qhub[num]->time=atoi(str)*60;
 						if((p=strchr(str,':'))!=NULL)
-							cfg.qhub[num]->time+=atoi(p+1); 
-					} 
+							cfg.qhub[num]->time+=atoi(p+1);
+					}
 				}
 				else if(i==1) {
 					sprintf(str,"%u",cfg.qhub[num]->freq
@@ -1355,8 +1356,8 @@ void qhub_edit(int num)
 						if(i && i<=1440)
 							cfg.qhub[num]->freq=1440/i;
 						else
-							cfg.qhub[num]->freq=0; 
-					} 
+							cfg.qhub[num]->freq=0;
+					}
 				}
 				break;
 			case __COUNTER__:
@@ -1395,8 +1396,8 @@ void qhub_edit(int num)
 				break;
 			case __COUNTER__:
 				qhub_sub_edit(num);
-				break; 
-		} 
+				break;
+		}
 	}
 }
 
@@ -1419,7 +1420,7 @@ void qhub_sub_edit(uint num)
 		"a `CONTROL.DAT` file extracted from a `.QWK` packet downloaded from\n"
 		"the QWK network hub."
 		;
-	char* qwk_ctrl_a_help = 
+	char* qwk_ctrl_a_help =
 		"`Ctrl-A Codes:`\n"
 		"\n"
 		"You are being prompted for the method of handling Ctrl-A attribute codes\n"
@@ -1501,7 +1502,7 @@ void qhub_sub_edit(uint num)
 			uifc.changes=1;
 			k++;
 			bar++;
-			continue; 
+			continue;
 		}
 		if((j&MSK_ON)==MSK_DEL) {
 			j&=MSK_OFF;
@@ -1510,10 +1511,10 @@ void qhub_sub_edit(uint num)
 				cfg.qhub[num]->sub[j]=cfg.qhub[num]->sub[j+1];
 				cfg.qhub[num]->mode[j]=cfg.qhub[num]->mode[j+1];
 				cfg.qhub[num]->conf[j]=cfg.qhub[num]->conf[j+1];
-				j++; 
+				j++;
 			}
 			uifc.changes=1;
-			continue; 
+			continue;
 		}
 		l=0;
 		while(1) {
@@ -1561,8 +1562,8 @@ void qhub_sub_edit(uint num)
 				m=getsub();
 				if(m!=-1) {
 					cfg.qhub[num]->sub[j]=cfg.sub[m];
-					uifc.changes=1; 
-				} 
+					uifc.changes=1;
+				}
 			}
 			else if(l==1) {
 				uifc.helpbuf=qwk_conf_num_help;
@@ -1570,7 +1571,7 @@ void qhub_sub_edit(uint num)
 				if(uifc.input(WIN_MID|WIN_SAV,0,0
 					,"Conference Number on Hub"
 					,str,5,K_NUMBER|K_EDIT) > 0)
-					cfg.qhub[num]->conf[j] = atoi(str); 
+					cfg.qhub[num]->conf[j] = atoi(str);
 			}
 			else if(l==2) {
 				strcpy(opt[0],"Strip out");
@@ -1587,9 +1588,9 @@ void qhub_sub_edit(uint num)
 				else if(m==1)
 					cfg.qhub[num]->mode[j]=QHUB_RETCTLA;
 				else if(m==2)
-					cfg.qhub[num]->mode[j]=QHUB_EXPCTLA; 
-			} 
-		} 
+					cfg.qhub[num]->mode[j]=QHUB_EXPCTLA;
+			}
+		}
 	}
 }
 
@@ -1627,7 +1628,7 @@ BOOL import_qwk_conferences(uint qhubnum)
 	FILE *fp;
 	if((fp = fopen(filename, "rt"))==NULL) {
 		uifc.msg("File Open Failure");
-		return FALSE; 
+		return FALSE;
 	}
 	uifc.pop("Importing Areas...");
 	long added = 0;
@@ -1660,7 +1661,7 @@ char *daystr(char days)
 	for(i=0;i<7;i++) {
 		if(days&(1<<i)) {
 			SAFECAT(str,wday[i]);
-			SAFECAT(str," "); 
+			SAFECAT(str," ");
 		}
 	}
 	return(str);
diff --git a/src/sbbs3/scfg/scfgnode.c b/src/sbbs3/scfg/scfgnode.c
index 2ae15f7cb1946cc33b2f8d0a07160ede5c797556..763e79f5e7057ec4ea668f502299daba17dcd4f5 100644
--- a/src/sbbs3/scfg/scfgnode.c
+++ b/src/sbbs3/scfg/scfgnode.c
@@ -15,7 +15,7 @@
  * http://www.synchro.net/source.html										*
  *																			*
  * Note: If this box doesn't appear square, then you need to fix your tabs.	*
- ****************************************************************************/ 
+ ****************************************************************************/
 
 #include "scfg.h"
 
@@ -32,10 +32,13 @@ static char* node_path_help =
 void node_menu()
 {
 	char	str[81],savnode=0;
+	char	cfg_filename[MAX_PATH + 1];
 	int 	i,j;
 	static int node_menu_dflt, node_bar;
 
+	SAFECOPY(cfg_filename, cfg.filename);
 	while(1) {
+		SAFECOPY(cfg.filename, cfg_filename);
 		for(i=0;i<cfg.sys_nodes;i++)
 			sprintf(opt[i],"Node %d",i+1);
 		opt[i][0]=0;
@@ -70,9 +73,9 @@ void node_menu()
 		if(i==-1) {
 			if(savnode) {
 				free_node_cfg(&cfg);
-				savnode=0; 
+				savnode=0;
 			}
-			return; 
+			return;
 		}
 		int msk = i & MSK_ON;
 		if(msk == MSK_DEL) {
@@ -91,7 +94,7 @@ void node_menu()
 				save_main_cfg(&cfg,backup_level);
 				refresh_cfg(&cfg);
 			}
-			continue; 
+			continue;
 		}
 		if(msk == MSK_INS && cfg.sys_nodes < MAX_NODES) {
 			SAFECOPY(cfg.node_dir,cfg.node_path[cfg.sys_nodes-1]);
@@ -132,7 +135,7 @@ void node_menu()
 			SAFECOPY(cfg.node_dir,cfg.node_path[i]);
 			load_node_cfg(&cfg,error, sizeof(error));
 			savnode=1;
-			continue; 
+			continue;
 		}
 		if(msk == MSK_PASTE) {
 			i&=MSK_OFF;
@@ -145,7 +148,7 @@ void node_menu()
 
 		if(savnode) {
 			free_node_cfg(&cfg);
-			savnode=0; 
+			savnode=0;
 		}
 		SAFECOPY(cfg.node_dir,cfg.node_path[i]);
 		prep_dir(cfg.ctrl_dir, cfg.node_dir, sizeof(cfg.node_dir));
@@ -157,7 +160,7 @@ void node_menu()
 		}
 		node_cfg();
 
-		free_node_cfg(&cfg); 
+		free_node_cfg(&cfg);
 	}
 }
 
@@ -244,11 +247,11 @@ void node_cfg()
 								,"Allow 8-bit Remote Input During Login",uifcYesNoOpts);
 							if(i==1 && !(cfg.node_misc&NM_7BITONLY)) {
 								cfg.node_misc|=NM_7BITONLY;
-								uifc.changes=1; 
+								uifc.changes=1;
 							}
 							else if(i==0 && (cfg.node_misc&NM_7BITONLY)) {
 								cfg.node_misc&=~NM_7BITONLY;
-								uifc.changes=1; 
+								uifc.changes=1;
 							}
 							break;
 						case 1:
@@ -263,11 +266,11 @@ void node_cfg()
 								,"Spinning Cursor at Pause Prompt",uifcYesNoOpts);
 							if(i==0 && cfg.node_misc&NM_NOPAUSESPIN) {
 								cfg.node_misc&=~NM_NOPAUSESPIN;
-								uifc.changes=1; 
+								uifc.changes=1;
 							}
 							else if(i==1 && !(cfg.node_misc&NM_NOPAUSESPIN)) {
 								cfg.node_misc|=NM_NOPAUSESPIN;
-								uifc.changes=1; 
+								uifc.changes=1;
 							}
 							break;
 						case 2:
@@ -284,11 +287,11 @@ void node_cfg()
 								,"Keep Node File Open",uifcYesNoOpts);
 							if(i==0 && cfg.node_misc&NM_CLOSENODEDAB) {
 								cfg.node_misc&=~NM_CLOSENODEDAB;
-								uifc.changes=1; 
+								uifc.changes=1;
 							}
 							else if(i==1 && !(cfg.node_misc&NM_CLOSENODEDAB)) {
 								cfg.node_misc|=NM_CLOSENODEDAB;
-								uifc.changes=1; 
+								uifc.changes=1;
 							}
 							break;
 						}
@@ -376,10 +379,10 @@ void node_cfg()
 							;
 							uifc.input(WIN_MID|WIN_SAV,0,10,"Text Directory"
 								,cfg.text_dir,sizeof(cfg.text_dir)-1,K_EDIT);
-							break; 
-					} 
+							break;
+					}
 				}
 				break;
-		} 
+		}
 	}
 }
diff --git a/src/sbbs3/scfg/scfgsrvr.c b/src/sbbs3/scfg/scfgsrvr.c
new file mode 100644
index 0000000000000000000000000000000000000000..1a8b42262a22500cd1bcff987ddb4c789c6b72f8
--- /dev/null
+++ b/src/sbbs3/scfg/scfgsrvr.c
@@ -0,0 +1,1465 @@
+/****************************************************************************
+ * @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 "scfg.h"
+#include "sbbs_ini.h"
+#include "netwrap.h"
+
+const char* strDisabled = "<disabled>";
+
+static const char* threshold(uint val)
+{
+	static char str[128];
+	if(val == 0)
+		return strDisabled;
+	SAFEPRINTF(str, "%u", val);
+	return str;
+}
+
+static const char* duration(uint val, bool verbose)
+{
+	static char str[128];
+	if(val == 0)
+		return strDisabled;
+	return verbose ? duration_to_vstr(val, str, sizeof(str)) : duration_to_str(val, str, sizeof(str));;
+}
+
+static const char* vduration(uint val)
+{
+	return duration(val, true);
+}
+
+static const char* maximum(uint val)
+{
+	static char str[128];
+	if(val == 0)
+		return "Unlimited";
+	SAFEPRINTF(str, "%u", val);
+	return str;
+}
+
+static void global_cfg(void)
+{
+	static int cur, bar;
+	char str[256];
+	char tmp[256];
+	global_startup_t startup = {0};
+
+	FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */false);
+	if(fp == NULL) {
+		uifc.msgf("Error opening %s", cfg.filename);
+		return;
+	}
+	sbbs_read_ini(
+		 fp
+		,cfg.filename
+		,&startup
+		,NULL
+		,NULL //&bbs_startup
+		,NULL
+		,NULL //&ftp_startup
+		,NULL
+		,NULL //&web_startup
+		,NULL
+		,NULL //&mail_startup
+		,NULL
+		,NULL //&services_startup
+		);
+	iniCloseFile(fp);
+	global_startup_t saved_startup = startup;
+
+	while(1) {
+		int i = 0;
+		sprintf(opt[i++], "%-40s%s", "Log Level", iniLogLevelStringList()[startup.log_level]);
+		sprintf(opt[i++], "%-40s%s", "TLS Error Level", iniLogLevelStringList()[startup.tls_error_level]);
+		sprintf(opt[i++], "%-40s%s", "Network Interfaces (IPv4/6)", strListCombine(startup.interfaces, tmp, sizeof(tmp), ", "));
+		sprintf(opt[i++], "%-40s%s", "Outbound Interface (IPv4)", IPv4AddressToStr(startup.outgoing4.s_addr, tmp, sizeof(tmp)));
+		sprintf(opt[i++], "%-40s%s", "Bind Retry Count", threshold(startup.bind_retry_count));
+		sprintf(opt[i++], "%-40s%s", "Bind Retry Delay", vduration(startup.bind_retry_delay));
+		sprintf(opt[i++], "%-40s%u ms", "Failed Login Delay", startup.login_attempt.delay);
+		sprintf(opt[i++], "%-40s%u ms", "Failed Login Throttle", startup.login_attempt.throttle);
+		sprintf(opt[i++], "%-40s%s", "Failed Login Hack Log Threshold", threshold(startup.login_attempt.hack_threshold));
+		sprintf(opt[i++], "%-40s%s", "Failed Login Temporary Ban Threshold", threshold(startup.login_attempt.tempban_threshold));
+		sprintf(opt[i++], "%-40s%s", "Failed Login Temporary Ban Duration"
+			,duration_to_vstr(startup.login_attempt.tempban_duration, tmp, sizeof(tmp)));
+		sprintf(opt[i++], "%-40s%s", "Failed Login Auto-Filter Threshold", threshold(startup.login_attempt.filter_threshold));
+		sprintf(opt[i++], "%-40s%s bytes", "JavaScript Heap Size", byte_count_to_str(startup.js.max_bytes, tmp, sizeof(tmp)));
+		sprintf(opt[i++], "%-40s%u ticks", "JavaScript Time Limit", startup.js.time_limit);
+		sprintf(opt[i++], "%-40s%u ticks", "JavaScript GC Interval ", startup.js.gc_interval);
+		sprintf(opt[i++], "%-40s%u ticks", "JavaScript Yield Interval", startup.js.yield_interval);
+		sprintf(opt[i++], "%-40s%s", "JavaScript Load Path", startup.js.load_path);
+		sprintf(opt[i++], "%-40s%s", "Semaphore File Check Interval", vduration(startup.sem_chk_freq));
+		opt[i][0] = '\0';
+
+		uifc.helpbuf=
+			"`Global Server Settings:`\n"
+			"\n"
+		;
+		switch(uifc.list(WIN_ACT|WIN_RHT|WIN_SAV|WIN_ESC, 0, 0, 0, &cur, &bar
+			,"Global Server Setttings",opt)) {
+			case 0:
+				uifc.list(WIN_MID|WIN_SAV, 0, 0, 0, &startup.log_level, 0, "Log Level", iniLogLevelStringList());
+				break;
+			case 1:
+				uifc.list(WIN_MID|WIN_SAV, 0, 0, 0, &startup.tls_error_level, 0, "TLS Error Log Level", iniLogLevelStringList());
+				break;
+			case 2:
+				strListCombine(startup.interfaces, str, sizeof(str), ", ");
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Inbound Network Interfaces (IPv4/6)", str, sizeof(str)-1, K_EDIT) >= 0) {
+					strListFree(&startup.interfaces);
+					strListSplitCopy(&startup.interfaces, str, ", ");
+					uifc.changes = true;
+				}
+				break;
+			case 3:
+				IPv4AddressToStr(startup.outgoing4.s_addr, str, sizeof(str));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Outbound Network Interface (IPv4)", str, sizeof(str)-1, K_EDIT) > 0)
+					startup.outgoing4.s_addr = parseIPv4Address(str);
+				break;
+			case 4:
+				SAFECOPY(str, threshold(startup.bind_retry_count));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Port Bind Retry Count", str, 6, K_EDIT) > 0)
+					startup.bind_retry_count = atoi(str);
+				break;
+			case 5:
+				SAFECOPY(str, duration(startup.bind_retry_delay, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Port Bind Retry Delay", str, 6, K_EDIT) > 0)
+					startup.bind_retry_delay = parse_duration(str);
+				break;
+			case 6:
+				SAFEPRINTF(str, "%u", startup.login_attempt.delay);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Millisecond Delay After Failed Login Attempts", str, 6, K_NUMBER|K_EDIT) > 0)
+					startup.login_attempt.delay = atoi(str);
+				break;
+			case 7:
+				SAFEPRINTF(str, "%u", startup.login_attempt.throttle);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Throttle multiplier (in milliseconds) for Failed Logins", str, 6, K_NUMBER|K_EDIT) > 0)
+					startup.login_attempt.throttle = atoi(str);
+				break;
+			case 8:
+				SAFEPRINTF(str, "%u", startup.login_attempt.hack_threshold);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Threshold for Logging Failed Logins to hack.log", str, 3, K_NUMBER|K_EDIT) > 0)
+					startup.login_attempt.hack_threshold = atoi(str);
+				break;
+			case 9:
+				SAFEPRINTF(str, "%u", startup.login_attempt.tempban_threshold);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Threshold for Temp-ban IPs of Failed Logins", str, 3, K_NUMBER|K_EDIT) > 0)
+					startup.login_attempt.tempban_threshold = atoi(str);
+				break;
+			case 10:
+				SAFECOPY(str, duration(startup.login_attempt.tempban_duration, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Duration of Temp-ban for Failed Logins", str, 6, K_EDIT) > 0)
+					startup.login_attempt.tempban_duration = parse_duration(str);
+				break;
+			case 11:
+				SAFEPRINTF(str, "%u", startup.login_attempt.filter_threshold);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Threshold for Filtering IPs of Failed Logins", str, 3, K_NUMBER|K_EDIT) > 0)
+					startup.login_attempt.filter_threshold = atoi(str);
+				break;
+			case 12:
+				byte_count_to_str(startup.js.max_bytes, str, sizeof(str));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "JavaScript Heap Size (Maximum Allocated Bytes)", str, 6, K_UPPER|K_EDIT) > 0)
+					startup.js.max_bytes = parse_byte_count(str, 1);
+				break;
+			case 13:
+				SAFEPRINTF(str, "%u", startup.js.time_limit);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "JavaScript Execution Time Limit (in ticks)", str, 6, K_NUMBER|K_EDIT) > 0)
+					startup.js.time_limit = atoi(str);
+				break;
+			case 14:
+				SAFEPRINTF(str, "%u", startup.js.gc_interval);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "JavaScript Garbage Collection Interval (in ticks)", str, 6, K_NUMBER|K_EDIT) > 0)
+					startup.js.gc_interval = atoi(str);
+				break;
+			case 15:
+				SAFEPRINTF(str, "%u", startup.js.yield_interval);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "JavaScript Yield Interval (in ticks)", str, 6, K_NUMBER|K_EDIT) > 0)
+					startup.js.yield_interval = atoi(str);
+				break;
+			case 16:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "JavaScript Load Library Path", startup.js.load_path, sizeof(startup.js.load_path) - 1, K_EDIT);
+				break;
+			case 17:
+				SAFECOPY(str, duration(startup.sem_chk_freq, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Semaphore File Check Interval", str, 6, K_EDIT) > 0)
+					startup.sem_chk_freq = parse_duration(str);
+				break;
+			default:
+				if(memcmp(&saved_startup, &startup, sizeof(startup)) != 0)
+					uifc.changes = true;
+				i = save_changes(WIN_MID);
+				if(i < 0)
+					continue;
+				if(i == 0) {
+					FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */true);
+					if(fp == NULL)
+						uifc.msgf("Error opening %s", cfg.filename);
+					else {
+						if(!sbbs_write_ini(
+							 fp
+							,&cfg
+							,&startup
+							,false
+							,NULL
+							,false
+							,NULL
+							,false
+							,NULL
+							,false
+							,NULL
+							,false
+							,NULL
+							))
+							uifc.msgf("Error writing %s", cfg.filename);
+						iniCloseFile(fp);
+					}
+				}
+				sbbs_free_ini(&startup
+					,NULL //&bbs_startup
+					,NULL //&ftp_startup
+					,NULL //&web_startup
+					,NULL //&mail_startup
+					,NULL //&services_startup
+					);
+				return;
+		}
+	}
+}
+
+static void termsrvr_cfg(void)
+{
+	static int cur, bar;
+	char str[256];
+	char tmp[256];
+	BOOL enabled = FALSE;
+	bbs_startup_t startup = {0};
+
+	FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */false);
+	if(fp == NULL) {
+		uifc.msgf("Error opening %s", cfg.filename);
+		return;
+	}
+	sbbs_read_ini(
+		 fp
+		,cfg.filename
+		,NULL
+		,&enabled
+		,&startup
+		,NULL
+		,NULL //&ftp_startup
+		,NULL
+		,NULL //&web_startup
+		,NULL
+		,NULL //&mail_startup
+		,NULL
+		,NULL //&services_startup
+		);
+	iniCloseFile(fp);
+	bbs_startup_t saved_startup = startup;
+
+	while(1) {
+		int i = 0;
+		sprintf(opt[i++], "%-30s%s", "Enabled", enabled ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Log Level", iniLogLevelStringList()[startup.log_level]);
+		sprintf(opt[i++], "%-30s%u", "First Node", startup.first_node);
+		sprintf(opt[i++], "%-30s%u", "Last Node", startup.last_node);
+		sprintf(opt[i++], "%-30s%s", "DOS Program Support", startup.options & BBS_OPT_NO_DOS ? "No" : "Yes");
+		sprintf(opt[i++], "%-30s%s", "SSH Support", startup.options & BBS_OPT_ALLOW_SSH ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "SSH Interfaces"
+			,startup.options & BBS_OPT_ALLOW_SSH ? strListCombine(startup.ssh_interfaces, tmp, sizeof(tmp), ", ") : "N/A");
+		sprintf(opt[i++], "%-30s%u", "SSH Port", startup.ssh_port);
+		sprintf(opt[i++], "%-30s%s", "SSH Connect Timeout"
+			,startup.options & BBS_OPT_ALLOW_SSH ? vduration(startup.ssh_connect_timeout) : "N/A");
+		sprintf(opt[i++], "%-30s%s", "Telnet Support", startup.options & BBS_OPT_NO_TELNET ? "No" : "Yes");
+		sprintf(opt[i++], "%-30s%s", "Telnet Interfaces"
+			,startup.options & BBS_OPT_NO_TELNET ? "N/A" : strListCombine(startup.telnet_interfaces, tmp, sizeof(tmp), ", "));
+		sprintf(opt[i++], "%-30s%u", "Telnet Port", startup.telnet_port);
+		sprintf(opt[i++], "%-30s%s", "Telnet Command Debug"
+			,startup.options & BBS_OPT_NO_TELNET ? "N/A" : startup.options & BBS_OPT_DEBUG_TELNET ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Telnet Send Go-Aheads"
+			,startup.options & BBS_OPT_NO_TELNET ? "N/A" : startup.options & BBS_OPT_NO_TELNET_GA ? "No" : "Yes");
+		sprintf(opt[i++], "%-30s%s", "RLogin Support", startup.options & BBS_OPT_ALLOW_RLOGIN ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "RLogin Interfaces"
+			,startup.options & BBS_OPT_ALLOW_RLOGIN ? strListCombine(startup.rlogin_interfaces, tmp, sizeof(tmp), ", ") : "N/A");
+		sprintf(opt[i++], "%-30s%u", "RLogin Port", startup.rlogin_port);
+		sprintf(opt[i++], "%-30s%u", "40 Column PETSCII Port", startup.pet40_port);
+		sprintf(opt[i++], "%-30s%u", "80 Column PETSCII Port", startup.pet80_port);
+		sprintf(opt[i++], "%-30s%s", "Max Concurrent Connections", maximum(startup.max_concurrent_connections));
+		sprintf(opt[i++], "%-30s%s", "Max Login Inactivity", vduration(startup.max_login_inactivity));
+		sprintf(opt[i++], "%-30s%s", "Max New User Inactivity", vduration(startup.max_newuser_inactivity));
+		sprintf(opt[i++], "%-30s%s", "Max User Inactivity", vduration(startup.max_session_inactivity));
+		sprintf(opt[i++], "%-30s%u ms", "Output Buffer Drain Timeout", startup.outbuf_drain_timeout);
+		sprintf(opt[i++], "%-30s%s", "Execute Timed Events", startup.options & BBS_OPT_NO_EVENTS ? "No" : "Yes");
+		sprintf(opt[i++], "%-30s%s", "Execute QWK-relatd Events"
+			,startup.options & BBS_OPT_NO_EVENTS ? "N/A" : startup.options & BBS_OPT_NO_QWK_EVENTS ? "No" : "Yes");
+		sprintf(opt[i++], "%-30s%s", "Lookup Client Hostname", startup.options & BBS_OPT_NO_HOST_LOOKUP ? "No" : "Yes");
+		if(!enabled)
+			i = 1;
+		opt[i][0] = '\0';
+
+		uifc.helpbuf=
+			"`Terminal Server Configuration:`\n"
+			"\n"
+		;
+		switch(uifc.list(WIN_ACT|WIN_ESC|WIN_RHT|WIN_SAV, 0, 0, 0, &cur, &bar
+			,"Terminal Server",opt)) {
+			case 0:
+				enabled = !enabled;
+				uifc.changes = true;
+				break;
+			case 1:
+				uifc.list(WIN_MID|WIN_SAV, 0, 0, 0, &startup.log_level, 0, "Log Level", iniLogLevelStringList());
+				break;
+			case 2:
+				SAFEPRINTF(str, "%u", startup.first_node);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "First Node Available For Terminal Logins", str, 3, K_NUMBER|K_EDIT) > 0)
+					startup.first_node = atoi(str);
+				break;
+			case 3:
+				SAFEPRINTF(str, "%u", startup.last_node);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Last Node Available For Terminal Logins", str, 3, K_NUMBER|K_EDIT) > 0)
+					startup.last_node = atoi(str);
+				break;
+			case 4:
+				startup.options ^= BBS_OPT_NO_DOS;
+				break;
+			case 5:
+				startup.options ^= BBS_OPT_ALLOW_SSH;
+				break;
+			case 6:
+				strListCombine(startup.ssh_interfaces, str, sizeof(str), ", ");
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "SSH Network Interfaces (IPv4/6)", str, sizeof(str)-1, K_EDIT) >= 0) {
+					strListFree(&startup.ssh_interfaces);
+					strListSplitCopy(&startup.ssh_interfaces, str, ", ");
+					uifc.changes = true;
+				}
+				break;
+			case 7:
+				SAFEPRINTF(str, "%u", startup.ssh_port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "SSH TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.ssh_port = atoi(str);
+				break;
+			case 8:
+				SAFECOPY(str, duration(startup.ssh_connect_timeout, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "SSH Connect Timeout", str, 6, K_EDIT) > 0)
+					startup.ssh_connect_timeout = parse_duration(str);
+				break;
+			case 9:
+				startup.options ^= BBS_OPT_NO_TELNET;
+				break;
+			case 10:
+				strListCombine(startup.telnet_interfaces, str, sizeof(str), ", ");
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Telnet Network Interfaces (IPv4/6)", str, sizeof(str)-1, K_EDIT) >= 0) {
+					strListFree(&startup.telnet_interfaces);
+					strListSplitCopy(&startup.telnet_interfaces, str, ", ");
+					uifc.changes = true;
+				}
+				break;
+			case 11:
+				SAFEPRINTF(str, "%u", startup.telnet_port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Telnet TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.telnet_port = atoi(str);
+				break;
+			case 12:
+				startup.options ^= BBS_OPT_DEBUG_TELNET;
+				break;
+			case 13:
+				startup.options ^= BBS_OPT_NO_TELNET_GA;
+				break;
+			case 14:
+				startup.options ^= BBS_OPT_ALLOW_RLOGIN;
+				break;
+			case 15:
+				strListCombine(startup.rlogin_interfaces, str, sizeof(str), ", ");
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "RLogin Network Interfaces (IPv4/6)", str, sizeof(str)-1, K_EDIT) >= 0) {
+					strListFree(&startup.rlogin_interfaces);
+					strListSplitCopy(&startup.rlogin_interfaces, str, ", ");
+					uifc.changes = true;
+				}
+				break;
+			case 16:
+				SAFEPRINTF(str, "%u", startup.rlogin_port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "RLogin TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.rlogin_port = atoi(str);
+				break;
+			case 17:
+				SAFEPRINTF(str, "%u", startup.pet40_port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "40 Column CBM/PETSCII TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.pet40_port = atoi(str);
+				break;
+			case 18:
+				SAFEPRINTF(str, "%u", startup.pet80_port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "80 Column CBM/PETSCII TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.pet80_port = atoi(str);
+				break;
+			case 19:
+				SAFECOPY(str, maximum(startup.max_concurrent_connections));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Concurrent (Unauthenticated) Connections", str, 10, K_EDIT) > 0)
+					startup.max_concurrent_connections = atoi(str);
+				break;
+			case 20:
+				SAFECOPY(str, duration(startup.max_login_inactivity, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Socket Inactivity at Login", str, 10, K_EDIT) > 0)
+					startup.max_login_inactivity = parse_duration(str);
+				break;
+			case 21:
+				SAFECOPY(str, duration(startup.max_newuser_inactivity, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Socket Inactivity at New User Registration", str, 10, K_EDIT) > 0)
+					startup.max_newuser_inactivity = parse_duration(str);
+				break;
+			case 22:
+				SAFECOPY(str, duration(startup.max_session_inactivity, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Socket Inactivity during User Session", str, 10, K_EDIT) > 0)
+					startup.max_session_inactivity = parse_duration(str);
+				break;
+			case 23:
+				SAFEPRINTF(str, "%u", startup.outbuf_drain_timeout);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Output Buffer Draing Timeout (milliseconds)", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.outbuf_drain_timeout = atoi(str);
+				break;
+			case 24:
+				startup.options ^= BBS_OPT_NO_EVENTS;
+				break;
+			case 25:
+				startup.options ^= BBS_OPT_NO_QWK_EVENTS;
+				break;
+			case 26:
+				startup.options ^= BBS_OPT_NO_HOST_LOOKUP;
+				break;
+			default:
+				if(memcmp(&saved_startup, &startup, sizeof(startup)) != 0)
+					uifc.changes = true;
+				i = save_changes(WIN_MID);
+				if(i < 0)
+					continue;
+				if(i == 0) {
+					FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */true);
+					if(fp == NULL)
+						uifc.msgf("Error opening %s", cfg.filename);
+					else {
+						if(!sbbs_write_ini(
+							 fp
+							,&cfg
+							,NULL
+							,enabled
+							,&startup
+							,false
+							,NULL
+							,false
+							,NULL
+							,false
+							,NULL
+							,false
+							,NULL
+							))
+							uifc.msgf("Error writing %s", cfg.filename);
+						iniCloseFile(fp);
+					}
+				}
+				sbbs_free_ini(NULL
+					,&startup
+					,NULL //&ftp_startup
+					,NULL //&web_startup
+					,NULL //&mail_startup
+					,NULL //&services_startup
+					);
+				return;
+		}
+	}
+}
+
+static void websrvr_cfg(void)
+{
+	static int cur, bar;
+	char tmp[256];
+	char str[256];
+	BOOL enabled = FALSE;
+	web_startup_t startup = {0};
+
+	FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */false);
+	if(fp == NULL) {
+		uifc.msgf("Error opening %s", cfg.filename);
+		return;
+	}
+	sbbs_read_ini(
+		 fp
+		,cfg.filename
+		,NULL
+		,NULL
+		,NULL
+		,NULL
+		,NULL //&ftp_startup
+		,&enabled
+		,&startup
+		,NULL
+		,NULL //&mail_startup
+		,NULL
+		,NULL //&services_startup
+		);
+	iniCloseFile(fp);
+	web_startup_t saved_startup = startup;
+
+	while(1) {
+		int i = 0;
+		sprintf(opt[i++], "%-30s%s", "Enabled", enabled ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Log Level", iniLogLevelStringList()[startup.log_level]);
+		sprintf(opt[i++], "%-30s%s", "HTTP Interfaces", strListCombine(startup.interfaces, tmp, sizeof(tmp), ", "));
+		sprintf(opt[i++], "%-30s%u", "HTTP Port", startup.port);
+		sprintf(opt[i++], "%-30s%s", "HTTPS Support", startup.options & WEB_OPT_ALLOW_TLS ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "HTTPS Interfaces"
+			,startup.options & WEB_OPT_ALLOW_TLS ? strListCombine(startup.tls_interfaces, tmp, sizeof(tmp), ", ") : "N/A");
+		sprintf(opt[i++], "%-30s%u", "HTTPS Port", startup.tls_port);
+		sprintf(opt[i++], "%-30s%s", "SSJS File Extension", startup.ssjs_ext);
+		sprintf(opt[i++], "%-30s%s", "Index Filenames", strListCombine(startup.index_file_name, tmp, sizeof(tmp), ", "));
+		sprintf(opt[i++], "%-30s%s", "Content Root Directory", startup.root_dir);
+		sprintf(opt[i++], "%-30s%s", "Error Sub-directory", startup.error_dir);
+		sprintf(opt[i++], "%-30s%s", "Strict Transport Security", startup.options & WEB_OPT_HSTS_SAFE ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Virtual Host Support", startup.options & WEB_OPT_VIRTUAL_HOSTS ? "Yes" : "No");
+		SAFECOPY(str, startup.logfile_base);
+		if(*str == '\0')
+			SAFEPRINTF(str, "[%slogs/http-*]", cfg.logs_dir);
+		sprintf(opt[i++], "%-30s%s", "Access Logging", startup.options & WEB_OPT_HTTP_LOGGING ? str : strDisabled);
+		sprintf(opt[i++], "%-30s%s", "Max Clients", maximum(startup.max_clients));
+		sprintf(opt[i++], "%-30s%s", "Max Inactivity", vduration(startup.max_inactivity));
+		sprintf(opt[i++], "%-30s%s", "Filebase Index Script", startup.file_index_script);
+		sprintf(opt[i++], "%-30s%s", "Filebase VPath Prefix", startup.file_vpath_prefix);
+		sprintf(opt[i++], "%-30s%s", "Filebase VPath for VHosts", startup.file_vpath_for_vhosts ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Authentication Methods", startup.default_auth_list);
+		sprintf(opt[i++], "%-30s%u ms", "Output Buffer Drain Timeout", startup.outbuf_drain_timeout);
+		sprintf(opt[i++], "%-30s%s", "Lookup Client Hostname", startup.options & BBS_OPT_NO_HOST_LOOKUP ? "No" : "Yes");
+		bool cgi_enabled = !(startup.options & WEB_OPT_NO_CGI);
+		sprintf(opt[i++], "%-30s%s", "CGI Support",  cgi_enabled ? "Yes" : "No");
+		if(cgi_enabled) {
+			sprintf(opt[i++], "%-30s%s", "CGI Directory", startup.cgi_dir);
+			sprintf(opt[i++], "%-30s%s", "CGI File Extensions", strListCombine(startup.cgi_ext, tmp, sizeof(tmp), ", "));
+			sprintf(opt[i++], "%-30s%s", "CGI Default Content-Type", startup.default_cgi_content);
+			sprintf(opt[i++], "%-30s%s", "CGI Max Inactivity", vduration(startup.max_cgi_inactivity));
+		}
+		if(!enabled)
+			i = 1;
+		opt[i][0] = '\0';
+
+		uifc.helpbuf=
+			"`Web Server Configuration:`\n"
+			"\n"
+		;
+		switch(uifc.list(WIN_ACT|WIN_ESC|WIN_RHT|WIN_SAV, 0, 0, 0, &cur, &bar
+			,"Web Server",opt)) {
+			case 0:
+				enabled = !enabled;
+				uifc.changes = true;
+				break;
+			case 1:
+				uifc.list(WIN_MID|WIN_SAV, 0, 0, 0, &startup.log_level, 0, "Log Level", iniLogLevelStringList());
+				break;
+			case 2:
+				strListCombine(startup.interfaces, str, sizeof(str), ", ");
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "HTTP Network Interfaces (IPv4/6)", str, sizeof(str)-1, K_EDIT) >= 0) {
+					strListFree(&startup.interfaces);
+					strListSplitCopy(&startup.interfaces, str, ", ");
+					uifc.changes = true;
+				}
+				break;
+			case 3:
+				SAFEPRINTF(str, "%u", startup.port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "HTTP TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.port = atoi(str);
+				break;
+			case 4:
+				startup.options ^= WEB_OPT_ALLOW_TLS;
+				break;
+			case 5:
+				strListCombine(startup.tls_interfaces, str, sizeof(str), ", ");
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "HTTP/TLS (HTTPS) Network Interfaces (IPv4/6)", str, sizeof(str)-1, K_EDIT) >= 0) {
+					strListFree(&startup.tls_interfaces);
+					strListSplitCopy(&startup.tls_interfaces, str, ", ");
+					uifc.changes = true;
+				}
+				break;
+			case 6:
+				SAFEPRINTF(str, "%u", startup.tls_port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "HTTP/TLS (HTTPS) TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.tls_port = atoi(str);
+				break;
+			case 7:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Server-side JavaScript File Extension"
+					, startup.ssjs_ext, sizeof(startup.ssjs_ext) -1, K_EDIT);
+				break;
+			case 8:
+				strListCombine(startup.index_file_name, str, sizeof(str), ", ");
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Index Filenames", str, sizeof(str)-1, K_EDIT) >= 0) {
+					strListFree(&startup.index_file_name);
+					strListSplitCopy(&startup.index_file_name, str, ", ");
+					uifc.changes = true;
+				}
+				break;
+			case 9:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Content Root Directory"
+					,startup.root_dir, sizeof(startup.root_dir)-1, K_EDIT);
+				break;
+			case 10:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Error Sub-directory"
+					,startup.error_dir, sizeof(startup.error_dir)-1, K_EDIT);
+				break;
+			case 11:
+				startup.options ^= WEB_OPT_HSTS_SAFE;
+				break;
+			case 12:
+				startup.options ^= WEB_OPT_VIRTUAL_HOSTS;
+				break;
+			case 13:
+				i = startup.options & WEB_OPT_HTTP_LOGGING ? 0 : 1;
+				i = uifc.list(WIN_SAV|WIN_MID, 0, 0, 0, &i, 0, "Log (to disk) HTTP Requests", uifcYesNoOpts);
+				if(i == 0) {
+					startup.options |= WEB_OPT_HTTP_LOGGING;
+					uifc.input(WIN_MID|WIN_SAV, 0, 0, "Base path/filename (blank = default)"
+						,startup.logfile_base, sizeof(startup.logfile_base)-1, K_EDIT);
+				} else if(i == 1)
+					startup.options &= ~WEB_OPT_HTTP_LOGGING;
+				break;
+			case 14:
+				SAFECOPY(str, maximum(startup.max_clients));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Client Count (0=unlimited)", str, 10, K_EDIT) > 0)
+					startup.max_clients = atoi(str);
+				break;
+			case 15:
+				SAFECOPY(str, duration(startup.max_inactivity, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Client Inactivity", str, 10, K_EDIT) > 0)
+					startup.max_inactivity = parse_duration(str);
+				break;
+			case 16:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Filebase Index Script"
+					,startup.file_index_script, sizeof(startup.file_index_script)-1, K_EDIT);
+				break;
+			case 17:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Filebase Virtual Path Prefix"
+					,startup.file_vpath_prefix, sizeof(startup.file_vpath_prefix)-1, K_EDIT);
+				break;
+			case 18:
+				startup.file_vpath_for_vhosts = !startup.file_vpath_for_vhosts;
+				break;
+			case 19:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Authentication Methods"
+					,startup.default_auth_list, sizeof(startup.default_auth_list)-1, K_EDIT);
+				break;
+			case 20:
+				SAFEPRINTF(str, "%u", startup.outbuf_drain_timeout);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Output Buffer Drain Timeout (milliseconds)"
+					,str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.outbuf_drain_timeout = atoi(str);
+				break;
+			case 21:
+				startup.options ^= BBS_OPT_NO_HOST_LOOKUP;
+				break;
+			case 22:
+				startup.options ^= WEB_OPT_NO_CGI;
+				break;
+			case 23:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "CGI Directory"
+					,startup.cgi_dir, sizeof(startup.cgi_dir)-1, K_EDIT);
+				break;
+			case 24:
+				strListCombine(startup.cgi_ext, str, sizeof(str), ", ");
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "CGI File Extensions", str, sizeof(str)-1, K_EDIT) >= 0) {
+					strListFree(&startup.cgi_ext);
+					strListSplitCopy(&startup.cgi_ext, str, ", ");
+					uifc.changes = true;
+				}
+				break;
+			case 25:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Default CGI MIME Content-Type"
+					,startup.default_cgi_content, sizeof(startup.default_cgi_content)-1, K_EDIT);
+				break;
+			case 26:
+				duration_to_str(startup.max_cgi_inactivity, str, sizeof(str));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum CGI Inactivity", str, 10, K_EDIT) > 0)
+					startup.max_cgi_inactivity = parse_duration(str);
+				break;
+			default:
+				if(memcmp(&saved_startup, &startup, sizeof(startup)) != 0)
+					uifc.changes = true;
+				i = save_changes(WIN_MID);
+				if(i < 0)
+					continue;
+				if(i == 0) {
+					FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */true);
+					if(fp == NULL)
+						uifc.msgf("Error opening %s", cfg.filename);
+					else {
+						if(!sbbs_write_ini(
+							 fp
+							,&cfg
+							,NULL
+							,FALSE
+							,NULL
+							,false
+							,NULL
+							,enabled
+							,&startup
+							,false
+							,NULL
+							,false
+							,NULL
+							))
+							uifc.msgf("Error writing %s", cfg.filename);
+						iniCloseFile(fp);
+					}
+				}
+				sbbs_free_ini(NULL
+					,NULL
+					,NULL //&ftp_startup
+					,&startup
+					,NULL //&mail_startup
+					,NULL //&services_startup
+					);
+				return;
+		}
+	}
+}
+
+static void ftpsrvr_cfg(void)
+{
+	static int cur, bar;
+	char tmp[256];
+	char str[256];
+	BOOL enabled = FALSE;
+	ftp_startup_t startup = {0};
+
+	FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */false);
+	if(fp == NULL) {
+		uifc.msgf("Error opening %s", cfg.filename);
+		return;
+	}
+	sbbs_read_ini(
+		 fp
+		,cfg.filename
+		,NULL
+		,NULL
+		,NULL
+		,&enabled
+		,&startup
+		,NULL
+		,NULL
+		,NULL
+		,NULL //&mail_startup
+		,NULL
+		,NULL //&services_startup
+		);
+	iniCloseFile(fp);
+	ftp_startup_t saved_startup = startup;
+
+	while(1) {
+		int i = 0;
+		sprintf(opt[i++], "%-30s%s", "Enabled", enabled ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Log Level", iniLogLevelStringList()[startup.log_level]);
+		sprintf(opt[i++], "%-30s%s", "Network Interfaces", strListCombine(startup.interfaces, tmp, sizeof(tmp), ", "));
+		sprintf(opt[i++], "%-30s%u, Data: %u", "Control Port", startup.port, startup.port - 1);
+		sprintf(opt[i++], "%-30s%s", "Passive Interface (IPv4)"
+			,startup.options & FTP_OPT_LOOKUP_PASV_IP ? "<automatic>" : IPv4AddressToStr(startup.pasv_ip_addr.s_addr, tmp, sizeof(tmp)));
+		sprintf(opt[i++], "%-30s%u - %u", "Passive Port Range", startup.pasv_port_low, startup.pasv_port_high);
+		sprintf(opt[i++], "%-30s%s", "Auto-generate Index File", startup.options & FTP_OPT_INDEX_FILE ? startup.index_file_name : strDisabled);
+		sprintf(opt[i++], "%-30s%s", "QWK Message Packet Transfers", startup.options & FTP_OPT_ALLOW_QWK ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "QWK Message Packet Timeout", startup.options & FTP_OPT_ALLOW_QWK ? vduration(startup.qwk_timeout) : "N/A");
+		sprintf(opt[i++], "%-30s%s", "Max Clients", maximum(startup.max_clients));
+		sprintf(opt[i++], "%-30s%s", "Max Inactivity", vduration(startup.max_inactivity));
+		sprintf(opt[i++], "%-30s%s", "Max Concurrent Connections", maximum(startup.max_concurrent_connections));
+		sprintf(opt[i++], "%-30s%s", "Sysop Filesystem Access", startup.options & FTP_OPT_NO_LOCAL_FSYS ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Allow Bounce Transfers", startup.options & FTP_OPT_ALLOW_BOUNCE ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Lookup Client Hostname", startup.options & BBS_OPT_NO_HOST_LOOKUP ? "No" : "Yes");
+		if(!enabled)
+			i = 1;
+		opt[i][0] = '\0';
+
+		uifc.helpbuf=
+			"`FTP Server Configuration:`\n"
+			"\n"
+		;
+		switch(uifc.list(WIN_ACT|WIN_ESC|WIN_RHT|WIN_SAV, 0, 0, 0, &cur, &bar
+			,"FTP Server",opt)) {
+			case 0:
+				enabled = !enabled;
+				uifc.changes = true;
+				break;
+			case 1:
+				uifc.list(WIN_MID|WIN_SAV, 0, 0, 0, &startup.log_level, 0, "Log Level", iniLogLevelStringList());
+				break;
+			case 2:
+				strListCombine(startup.interfaces, str, sizeof(str), ", ");
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Network Interfaces (IPv4/6)", str, sizeof(str)-1, K_EDIT) >= 0) {
+					strListFree(&startup.interfaces);
+					strListSplitCopy(&startup.interfaces, str, ", ");
+					uifc.changes = true;
+				}
+				break;
+			case 3:
+				SAFEPRINTF(str, "%u", startup.port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Control TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.port = atoi(str);
+				break;
+			case 4:
+				i = startup.options & FTP_OPT_LOOKUP_PASV_IP ? 0 : 1;
+				i = uifc.list(WIN_MID|WIN_SAV, 0, 0, 0, &i, 0, "Automatically Detect Public IP Address", uifcYesNoOpts);
+				if(i == 0)
+					startup.options |= FTP_OPT_LOOKUP_PASV_IP;
+				else if(i == 1) {
+					startup.options &= ~FTP_OPT_LOOKUP_PASV_IP;
+					IPv4AddressToStr(startup.pasv_ip_addr.s_addr, str, sizeof(str));
+					if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "IPv4 Address for Passive Connections", str, sizeof(str)-1, K_EDIT) > 0)
+						startup.pasv_ip_addr.s_addr = parseIPv4Address(str);
+				}
+				break;
+			case 5:
+				SAFEPRINTF(str, "%u", startup.pasv_port_low);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Lowest Passive TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.pasv_port_low = atoi(str);
+				else
+					break;
+				SAFEPRINTF(str, "%u", startup.pasv_port_high);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Highest Passive TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.pasv_port_high = atoi(str);
+				break;
+			case 6:
+				i = startup.options & FTP_OPT_INDEX_FILE ? 0 : 1;
+				i = uifc.list(WIN_SAV|WIN_MID, 0, 0, 0, &i, 0, "Autogenerate Index Files", uifcYesNoOpts);
+				if(i == 0) {
+					startup.options |= FTP_OPT_INDEX_FILE;
+					uifc.input(WIN_MID|WIN_SAV, 0, 0, "Index Filename"
+						,startup.index_file_name, sizeof(startup.index_file_name)-1, K_EDIT);
+				} else if(i == 1)
+					startup.options &= ~FTP_OPT_INDEX_FILE;
+				break;
+			case 7:
+				startup.options ^= FTP_OPT_ALLOW_QWK;
+				break;
+			case 8:
+				SAFECOPY(str, duration(startup.qwk_timeout, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "QWK Message Packet Creation Timeout", str, 10, K_EDIT) > 0)
+					startup.qwk_timeout = parse_duration(str);
+				break;
+			case 9:
+				SAFECOPY(str, maximum(startup.max_clients));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Client Count (0=unlimited)", str, 10, K_EDIT) > 0)
+					startup.max_clients = atoi(str);
+				break;
+			case 10:
+				SAFECOPY(str, duration(startup.max_inactivity, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Client Inactivity", str, 10, K_EDIT) > 0)
+					startup.max_inactivity = parse_duration(str);
+				break;
+			case 11:
+				SAFECOPY(str, maximum(startup.max_concurrent_connections));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Concurrent (Unauthenticated) Connections", str, 10, K_EDIT) > 0)
+					startup.max_concurrent_connections = atoi(str);
+				break;
+			case 12:
+				startup.options ^= FTP_OPT_NO_LOCAL_FSYS;
+				break;
+			case 13:
+				startup.options ^= FTP_OPT_ALLOW_BOUNCE;
+				break;
+			case 14:
+				startup.options ^= BBS_OPT_NO_HOST_LOOKUP;
+				break;
+			default:
+				if(memcmp(&saved_startup, &startup, sizeof(startup)) != 0)
+					uifc.changes = true;
+				i = save_changes(WIN_MID);
+				if(i < 0)
+					continue;
+				if(i == 0) {
+					FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */true);
+					if(fp == NULL)
+						uifc.msgf("Error opening %s", cfg.filename);
+					else {
+						if(!sbbs_write_ini(
+							 fp
+							,&cfg
+							,NULL
+							,FALSE
+							,NULL
+							,enabled
+							,&startup
+							,false
+							,NULL
+							,false
+							,NULL
+							,false
+							,NULL
+							))
+							uifc.msgf("Error writing %s", cfg.filename);
+						iniCloseFile(fp);
+					}
+				}
+				sbbs_free_ini(NULL
+					,NULL
+					,&startup
+					,NULL
+					,NULL //&mail_startup
+					,NULL //&services_startup
+					);
+				return;
+		}
+	}
+}
+
+static void mailsrvr_cfg(void)
+{
+	static int cur, bar;
+	char tmp[256];
+	char str[256];
+	const char* p;
+	BOOL enabled = FALSE;
+	mail_startup_t startup = {0};
+
+	FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */false);
+	if(fp == NULL) {
+		uifc.msgf("Error opening %s", cfg.filename);
+		return;
+	}
+	sbbs_read_ini(
+		 fp
+		,cfg.filename
+		,NULL
+		,NULL
+		,NULL
+		,NULL
+		,NULL
+		,NULL
+		,NULL
+		,&enabled
+		,&startup
+		,NULL
+		,NULL //&services_startup
+		);
+	iniCloseFile(fp);
+	mail_startup_t saved_startup = startup;
+
+	while(1) {
+		int i = 0;
+		sprintf(opt[i++], "%-30s%s", "Enabled", enabled ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Log Level", iniLogLevelStringList()[startup.log_level]);
+		sprintf(opt[i++], "%-30s%s", "SMTP Interfaces", strListCombine(startup.interfaces, tmp, sizeof(tmp), ", "));
+		sprintf(opt[i++], "%-30s%u", "SMTP Port", startup.smtp_port);
+		sprintf(opt[i++], "%-30s%s", "Submission Support", startup.options & MAIL_OPT_USE_SUBMISSION_PORT ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%u", "Submission Port", startup.submission_port);
+		sprintf(opt[i++], "%-30s%s", "Submission/TLS Support", startup.options & MAIL_OPT_TLS_SUBMISSION ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%u", "Submission/TLS Port", startup.submissions_port);
+		sprintf(opt[i++], "%-30s%s", "POP3 Support", startup.options & MAIL_OPT_ALLOW_POP3 ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "POP3 Interfaces"
+			,startup.options & (MAIL_OPT_ALLOW_POP3 | MAIL_OPT_TLS_POP3)
+				? strListCombine(startup.pop3_interfaces, tmp, sizeof(tmp), ", ") : "N/A");
+		sprintf(opt[i++], "%-30s%u", "POP3 Port", startup.pop3_port);
+		sprintf(opt[i++], "%-30s%s", "POP3/TLS Support", startup.options & MAIL_OPT_TLS_POP3 ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%u", "POP3/TLS Port", startup.pop3s_port);
+		sprintf(opt[i++], "%-30s%s", "Max Clients", maximum(startup.max_clients));
+		sprintf(opt[i++], "%-30s%s", "Max Inactivity", vduration(startup.max_inactivity));
+		sprintf(opt[i++], "%-30s%s", "Max Concurrent Connections", maximum(startup.max_concurrent_connections));
+		sprintf(opt[i++], "%-30s%s", "Max Recipients Per Message", maximum(startup.max_recipients));
+		sprintf(opt[i++], "%-30s%s", "Max Messages Waiting", maximum(startup.max_msgs_waiting));
+		sprintf(opt[i++], "%-30s%s bytes", "Max Receive Message Size", byte_count_to_str(startup.max_msg_size, tmp, sizeof(tmp)));
+		sprintf(opt[i++], "%-30s%s", "Allow Users to Relay Mail", startup.options & MAIL_OPT_ALLOW_RELAY ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Receive By Sysop Aliases", startup.options & MAIL_OPT_ALLOW_SYSOP_ALIASES ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Notify Local Recipients", startup.options & MAIL_OPT_NO_NOTIFY ? "No" : "Yes");
+		sprintf(opt[i++], "%-30s%s", "Lookup Client Hostname", startup.options & BBS_OPT_NO_HOST_LOOKUP ? "No" : "Yes");
+		sprintf(opt[i++], "%-30s%s", "Check Headers against DNSBL", startup.options & MAIL_OPT_DNSBL_CHKRECVHDRS ? "Yes" : "No");
+		if(startup.options & MAIL_OPT_DNSBL_REFUSE)
+			p = "Refuse Session";
+		else if(startup.options & MAIL_OPT_DNSBL_IGNORE)
+			p = "Silently Ignore";
+		else if(startup.options & MAIL_OPT_DNSBL_BADUSER)
+			p = "Refuse Mail";
+		else
+			p = "Tag Mail";
+		sprintf(opt[i++], "%-30s%s%s", "Blacklisted Servers", startup.options & MAIL_OPT_DNSBL_THROTTLE ? "Throttle and " : "", p);
+		sprintf(opt[i++], "%-30s%s", "Hash Blacklisted Messages", startup.options & MAIL_OPT_DNSBL_SPAMHASH ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Auto-exempt Recipients", startup.options & MAIL_OPT_NO_AUTO_EXEMPT ? "No" : "Yes");
+		sprintf(opt[i++], "%-30s%s", "Kill SPAM When Read", startup.options & MAIL_OPT_KILL_READ_SPAM ? "Yes": "No");
+		sprintf(opt[i++], "%-30s%s", "SendMail Support", startup.options & MAIL_OPT_NO_SENDMAIL ? "No" : "Yes");
+		if(!(startup.options & MAIL_OPT_NO_SENDMAIL)) {
+			bool applicable = startup.options & MAIL_OPT_RELAY_TX;
+			sprintf(opt[i++], "%-30s%s", "SendMail Delivery", applicable ? "Relay" : "Direct");
+			sprintf(opt[i++], "%-30s%s", "SendMail Relay Server", applicable ? startup.relay_server : "N/A");
+			sprintf(opt[i++], "%-30s%u", "SendMail Relay Port", startup.relay_port);
+			sprintf(opt[i++], "%-30s%s", "SendMail Relay User", applicable ? startup.relay_user : "N/A");
+			sprintf(opt[i++], "%-30s%s", "SendMail Relay Password", applicable ? startup.relay_pass : "N/A");
+			if(startup.options & MAIL_OPT_RELAY_AUTH_PLAIN)
+				p = "Plain";
+			else if(startup.options & MAIL_OPT_RELAY_AUTH_LOGIN)
+				p = "Login";
+			else if(startup.options & MAIL_OPT_RELAY_AUTH_CRAM_MD5)
+				p = "CRAM-MD5";
+			else
+				p = "None";
+			sprintf(opt[i++], "%-30s%s", "SendMail Relay Auth", applicable ? p : "N/A");
+			sprintf(opt[i++], "%-30s%u", "SendMail Max Attempts", startup.max_delivery_attempts);
+			sprintf(opt[i++], "%-30s%s", "SendMail Rescan Interval", vduration(startup.rescan_frequency));
+			sprintf(opt[i++], "%-30s%s", "SendMail Connect Timeout", vduration(startup.connect_timeout));
+		}
+		if(!enabled)
+			i = 1;
+		opt[i][0] = '\0';
+
+		uifc.helpbuf=
+			"`Mail Server Configuration:`\n"
+			"\n"
+		;
+		switch(uifc.list(WIN_ACT|WIN_ESC|WIN_RHT|WIN_SAV, 0, 0, 0, &cur, &bar
+			,"Mail Server",opt)) {
+			case 0:
+				enabled = !enabled;
+				uifc.changes = true;
+				break;
+			case 1:
+				uifc.list(WIN_MID|WIN_SAV, 0, 0, 0, &startup.log_level, 0, "Log Level", iniLogLevelStringList());
+				break;
+			case 2:
+				strListCombine(startup.interfaces, str, sizeof(str), ", ");
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "SMTP Network Interfaces (IPv4/6)", str, sizeof(str)-1, K_EDIT) >= 0) {
+					strListFree(&startup.interfaces);
+					strListSplitCopy(&startup.interfaces, str, ", ");
+					uifc.changes = true;
+				}
+				break;
+			case 3:
+				SAFEPRINTF(str, "%u", startup.smtp_port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "SMTP TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.smtp_port = atoi(str);
+				break;
+			case 4:
+				startup.options ^= MAIL_OPT_USE_SUBMISSION_PORT;
+				break;
+			case 5:
+				SAFEPRINTF(str, "%u", startup.submission_port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "SMTP-Submission TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.submission_port = atoi(str);
+				break;
+			case 6:
+				startup.options ^= MAIL_OPT_TLS_SUBMISSION;
+				break;
+			case 7:
+				SAFEPRINTF(str, "%u", startup.submissions_port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "SMTP-Submission/TLS TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.submissions_port = atoi(str);
+				break;
+			case 8:
+				startup.options ^= MAIL_OPT_ALLOW_POP3;
+				break;
+			case 9:
+				strListCombine(startup.pop3_interfaces, str, sizeof(str), ", ");
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "POP3 Network Interfaces (IPv4/6)", str, sizeof(str)-1, K_EDIT) >= 0) {
+					strListFree(&startup.pop3_interfaces);
+					strListSplitCopy(&startup.pop3_interfaces, str, ", ");
+					uifc.changes = true;
+				}
+				break;
+			case 10:
+				SAFEPRINTF(str, "%u", startup.pop3_port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "POP3 TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.pop3_port = atoi(str);
+				break;
+			case 11:
+				startup.options ^= MAIL_OPT_TLS_POP3;
+				break;
+			case 12:
+				SAFEPRINTF(str, "%u", startup.pop3s_port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "POP3/TLS TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.pop3s_port = atoi(str);
+				break;
+			case 13:
+				SAFECOPY(str, maximum(startup.max_clients));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Client Count (0=unlimited)", str, 10, K_EDIT) > 0)
+					startup.max_clients = atoi(str);
+				break;
+			case 14:
+				SAFECOPY(str, duration(startup.max_inactivity, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Client Inactivity", str, 10, K_EDIT) > 0)
+					startup.max_inactivity = parse_duration(str);
+				break;
+			case 15:
+				SAFECOPY(str, maximum(startup.max_concurrent_connections));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Concurrent (Unauthenticated) Connections", str, 10, K_EDIT) > 0)
+					startup.max_concurrent_connections = atoi(str);
+				break;
+			case 16:
+				SAFECOPY(str, maximum(startup.max_recipients));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Recipients per Message", str, 10, K_EDIT) > 0)
+					startup.max_recipients = atoi(str);
+				break;
+			case 17:
+				SAFECOPY(str, maximum(startup.max_msgs_waiting));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Messages Waiting per User", str, 10, K_EDIT) > 0)
+					startup.max_msgs_waiting = atoi(str);
+				break;
+			case 18:
+				byte_count_to_str(startup.max_msg_size, str, sizeof(str));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Received Message Size (in bytes)", str, 10, K_EDIT) > 0)
+					startup.max_msg_size = parse_byte_count(str, 1);
+				break;
+			case 19:
+				startup.options ^= MAIL_OPT_ALLOW_RELAY;
+				break;
+			case 20:
+				startup.options ^= MAIL_OPT_ALLOW_SYSOP_ALIASES;
+				break;
+			case 21:
+				startup.options ^= MAIL_OPT_NO_NOTIFY;
+				break;
+			case 22:
+				startup.options ^= BBS_OPT_NO_HOST_LOOKUP;
+				break;
+			case 23:
+				startup.options ^= MAIL_OPT_DNSBL_CHKRECVHDRS;
+				break;
+			case 24:
+				i = 0;
+				strcpy(opt[i++], "Refuse Session");
+				strcpy(opt[i++], "Silently Ignore");
+				strcpy(opt[i++], "Refuse Mail");
+				strcpy(opt[i++], "Tag Mail");
+				opt[i][0] = '\0';
+				if(startup.options & MAIL_OPT_DNSBL_REFUSE)
+					i = 0;
+				else if(startup.options & MAIL_OPT_DNSBL_IGNORE)
+					i = 1;
+				else if(startup.options & MAIL_OPT_DNSBL_BADUSER)
+					i = 2;
+				else
+					i = 3;
+				if(uifc.list(WIN_MID|WIN_SAV, 0, 0, 0, &i, 0, "DNS-Blacklisted Servers", opt) < 0)
+					break;
+				startup.options &= ~(MAIL_OPT_DNSBL_REFUSE | MAIL_OPT_DNSBL_IGNORE | MAIL_OPT_DNSBL_BADUSER);
+				switch(i) {
+					case 0:
+						startup.options |= MAIL_OPT_DNSBL_REFUSE;
+						break;
+					case 1:
+						startup.options |= MAIL_OPT_DNSBL_IGNORE;
+						break;
+					case 2:
+						startup.options |= MAIL_OPT_DNSBL_BADUSER;
+				}
+				i = startup.options & MAIL_OPT_DNSBL_THROTTLE ? 0 : 1;
+				if(uifc.list(WIN_MID|WIN_SAV, 0, 0, 0, &i, 0, "Throttle DNS-Blacklisted Servers", uifcYesNoOpts) < 0)
+					break;
+				if(i == 0)
+					startup.options |= MAIL_OPT_DNSBL_THROTTLE;
+				else
+					startup.options &= ~MAIL_OPT_DNSBL_THROTTLE;
+				break;
+			case 25:
+				startup.options ^= MAIL_OPT_DNSBL_SPAMHASH;
+				break;
+			case 26:
+				startup.options ^= MAIL_OPT_NO_AUTO_EXEMPT;
+				break;
+			case 27:
+				startup.options ^= MAIL_OPT_KILL_READ_SPAM;
+				break;
+			case 28:
+				startup.options ^= MAIL_OPT_NO_SENDMAIL;
+				break;
+			case 29:
+				startup.options ^= MAIL_OPT_RELAY_TX;
+				break;
+			case 30:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Relay Server Address", startup.relay_server, sizeof(startup.relay_server)-1, K_EDIT);
+				break;
+			case 31:
+				SAFEPRINTF(str, "%u", startup.relay_port);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Relay Server TCP Port", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.relay_port = atoi(str);
+				break;
+			case 32:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Relay Server Username", startup.relay_user, sizeof(startup.relay_user)-1, K_EDIT);
+				break;
+			case 33:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Relay Server Password", startup.relay_pass, sizeof(startup.relay_pass)-1, K_EDIT);
+				break;
+			case 34:
+				i = 0;
+				strcpy(opt[i++], "Plain");
+				strcpy(opt[i++], "Login");
+				strcpy(opt[i++], "CRAM-MD5");
+				strcpy(opt[i++], "None");
+				opt[i][0] = '\0';
+				if(startup.options & MAIL_OPT_RELAY_AUTH_PLAIN)
+					i = 0;
+				else if(startup.options & MAIL_OPT_RELAY_AUTH_LOGIN)
+					i = 1;
+				else if(startup.options & MAIL_OPT_RELAY_AUTH_CRAM_MD5)
+					i = 2;
+				else
+					i = 3;
+				if(uifc.list(WIN_MID|WIN_SAV, 0, 0, 0, &i, 0, "Relay Server Authentication Method", opt) < 0)
+					break;
+				startup.options &= ~(MAIL_OPT_RELAY_AUTH_PLAIN | MAIL_OPT_RELAY_AUTH_LOGIN | MAIL_OPT_RELAY_AUTH_CRAM_MD5);
+				switch(i) {
+					case 0:
+						startup.options |= MAIL_OPT_RELAY_AUTH_PLAIN;
+						break;
+					case 1:
+						startup.options |= MAIL_OPT_RELAY_AUTH_LOGIN;
+						break;
+					case 2:
+						startup.options |= MAIL_OPT_RELAY_AUTH_CRAM_MD5;
+						break;
+				}
+				break;
+			case 35:
+				SAFEPRINTF(str, "%u", startup.max_delivery_attempts);
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Number of SendMail Delivery Attempts", str, 5, K_NUMBER|K_EDIT) > 0)
+					startup.max_delivery_attempts = atoi(str);
+				break;
+			case 36:
+				SAFECOPY(str, duration(startup.rescan_frequency, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "MailBase Re-scan Interval", str, 5, K_EDIT) > 0)
+					startup.rescan_frequency = parse_duration(str);
+				break;
+			case 37:
+				SAFECOPY(str, duration(startup.connect_timeout, false));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "SendMail Connect Timeout", str, 5, K_EDIT) > 0)
+					startup.connect_timeout = parse_duration(str);
+				break;
+			default:
+				if(memcmp(&saved_startup, &startup, sizeof(startup)) != 0)
+					uifc.changes = true;
+				i = save_changes(WIN_MID);
+				if(i < 0)
+					continue;
+				if(i == 0) {
+					FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */true);
+					if(fp == NULL)
+						uifc.msgf("Error opening %s", cfg.filename);
+					else {
+						if(!sbbs_write_ini(
+							 fp
+							,&cfg
+							,NULL
+							,FALSE
+							,NULL
+							,false
+							,NULL
+							,false
+							,NULL
+							,enabled
+							,&startup
+							,false
+							,NULL
+							))
+							uifc.msgf("Error writing %s", cfg.filename);
+						iniCloseFile(fp);
+					}
+				}
+				sbbs_free_ini(NULL
+					,NULL
+					,NULL
+					,NULL
+					,&startup
+					,NULL //&services_startup
+					);
+				return;
+		}
+	}
+}
+
+static void services_cfg(void)
+{
+	static int cur, bar;
+	char tmp[256];
+	char str[256];
+	BOOL enabled = FALSE;
+	services_startup_t startup = {0};
+
+	FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */false);
+	if(fp == NULL) {
+		uifc.msgf("Error opening %s", cfg.filename);
+		return;
+	}
+	sbbs_read_ini(
+		 fp
+		,cfg.filename
+		,NULL
+		,NULL
+		,NULL
+		,NULL
+		,NULL
+		,NULL
+		,NULL
+		,NULL
+		,NULL
+		,&enabled
+		,&startup
+		);
+	iniCloseFile(fp);
+	services_startup_t saved_startup = startup;
+
+	while(1) {
+		int i = 0;
+		sprintf(opt[i++], "%-30s%s", "Enabled", enabled ? "Yes" : "No");
+		sprintf(opt[i++], "%-30s%s", "Log Level", iniLogLevelStringList()[startup.log_level]);
+		sprintf(opt[i++], "%-30s%s", "Network Interfaces", strListCombine(startup.interfaces, tmp, sizeof(tmp), ", "));
+		sprintf(opt[i++], "%-30s%s", "Lookup Client Hostname", startup.options & BBS_OPT_NO_HOST_LOOKUP ? "No" : "Yes");
+		sprintf(opt[i++], "%-30s%s", "Configuration File", startup.services_ini);
+		if(!enabled)
+			i = 1;
+		opt[i][0] = '\0';
+
+		uifc.helpbuf=
+			"`Services Server Configuration:`\n"
+			"\n"
+		;
+		switch(uifc.list(WIN_ACT|WIN_ESC|WIN_RHT|WIN_SAV, 0, 0, 0, &cur, &bar
+			,"Services Server",opt)) {
+			case 0:
+				enabled = !enabled;
+				uifc.changes = true;
+				break;
+			case 1:
+				uifc.list(WIN_MID|WIN_SAV, 0, 0, 0, &startup.log_level, 0, "Log Level", iniLogLevelStringList());
+				break;
+			case 2:
+				strListCombine(startup.interfaces, str, sizeof(str), ", ");
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Network Interfaces (IPv4/6)", str, sizeof(str)-1, K_EDIT) >= 0) {
+					strListFree(&startup.interfaces);
+					strListSplitCopy(&startup.interfaces, str, ", ");
+					uifc.changes = true;
+				}
+				break;
+			case 3:
+				startup.options ^= BBS_OPT_NO_HOST_LOOKUP;
+				break;
+			case 4:
+				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Services Configuration File", startup.services_ini, sizeof(startup.services_ini)-1, K_EDIT);
+				break;
+			default:
+				if(memcmp(&saved_startup, &startup, sizeof(startup)) != 0)
+					uifc.changes = true;
+				i = save_changes(WIN_MID);
+				if(i < 0)
+					continue;
+				if(i == 0) {
+					FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */true);
+					if(fp == NULL)
+						uifc.msgf("Error opening %s", cfg.filename);
+					else {
+						if(!sbbs_write_ini(
+							 fp
+							,&cfg
+							,NULL
+							,FALSE
+							,NULL
+							,false
+							,NULL
+							,false
+							,NULL
+							,false
+							,NULL
+							,enabled
+							,&startup
+							))
+							uifc.msgf("Error writing %s", cfg.filename);
+						iniCloseFile(fp);
+					}
+				}
+				sbbs_free_ini(NULL
+					,NULL
+					,NULL
+					,NULL
+					,NULL
+					,&startup
+					);
+				return;
+		}
+	}
+}
+
+void server_cfg(void)
+{
+	static int srvr_dflt;
+	BOOL run_bbs, run_ftp, run_web, run_mail, run_services;
+
+	sbbs_get_ini_fname(cfg.filename, cfg.ctrl_dir);
+
+	while(1) {
+		FILE* fp = iniOpenFile(cfg.filename, /* for_modify? */false);
+		if(fp == NULL) {
+			uifc.msgf("Error opening %s", cfg.filename);
+			return;
+		}
+		sbbs_read_ini(
+			 fp
+			,cfg.filename
+			,NULL //&global_startup
+			,&run_bbs
+			,NULL //&bbs_startup
+			,&run_ftp
+			,NULL //&ftp_startup
+			,&run_web
+			,NULL //&web_startup
+			,&run_mail
+			,NULL //&mail_startup
+			,&run_services
+			,NULL //&services_startup
+			);
+		iniCloseFile(fp);
+
+		int i = 0;
+		strcpy(opt[i++], "Global Settings");
+		sprintf(opt[i++], "%-27s%s", "Terminal Server", run_bbs ? "Enabled" : strDisabled);
+		sprintf(opt[i++], "%-27s%s", "Web Server", run_web ? "Enabled" : strDisabled);
+		sprintf(opt[i++], "%-27s%s", "FTP Server", run_ftp ? "Enabled" : strDisabled);
+		sprintf(opt[i++], "%-27s%s", "Mail Server", run_mail ? "Enabled" : strDisabled);
+		sprintf(opt[i++], "%-27s%s", "Services Server", run_services ? "Enabled" : strDisabled);
+		opt[i][0] = '\0';
+
+		uifc.helpbuf=
+			"`Server Configuration:`\n"
+			"\n"
+			"Here you can configure initialization settings of the various TCP/IP\n"
+			"servers that are integrated into Synchronet BBS Software.\n"
+			"\n"
+			"For additinal advanced Synchronet server initialization settings, see\n"
+			"the `ctrl/sbbs.ini` file and `https://wiki.synchro.net/config:sbbs.ini`\n"
+			"for reference.\n"
+		;
+		i = uifc.list(WIN_ORG|WIN_ACT,0,0,0,&srvr_dflt,0, "Server Configuration",opt);
+		switch(i) {
+			case 0:
+				global_cfg();
+				break;
+			case 1:
+				termsrvr_cfg();
+				break;
+			case 2:
+				websrvr_cfg();
+				break;
+			case 3:
+				ftpsrvr_cfg();
+				break;
+			case 4:
+				mailsrvr_cfg();
+				break;
+			case 5:
+				services_cfg();
+				break;
+			default:
+				return;
+		}
+	}
+}
diff --git a/src/sbbs3/scfgdefs.h b/src/sbbs3/scfgdefs.h
index 13fb795ec0a8f1730a5b53b62a890c11c716870b..68ace40f6759f3ecba69aa6ab86c73b738067e2a 100644
--- a/src/sbbs3/scfgdefs.h
+++ b/src/sbbs3/scfgdefs.h
@@ -286,7 +286,7 @@ enum xedit_soft_cr {						// What to do with so-called "Soft CRs"
 	XEDIT_SOFT_CR_EXPAND,
 	XEDIT_SOFT_CR_STRIP,
 	XEDIT_SOFT_CR_RETAIN
-};								
+};
 
 typedef struct {							/* External Editors */
 	char		code[LEN_CODE+1],
@@ -392,10 +392,11 @@ struct mqtt_cfg {
 	} tls;
 };
 
-typedef struct 
+typedef struct
 {
 	DWORD			size;				/* sizeof(scfg_t) */
 	BOOL			prepped;			/* TRUE if prep_cfg() has been used */
+	char			filename[MAX_PATH + 1]; // last-loaded cfg file path/name
 
 	grp_t			**grp;				/* Each message group */
 	uint16_t		total_grps; 		/* Total number of groups */
diff --git a/src/sbbs3/scfglib1.c b/src/sbbs3/scfglib1.c
index 6827d8aa38d92b589ea5006130642c2e1a40c383..d521a5f9e0e97898f88d4cf07073ecd52fd4a79e 100644
--- a/src/sbbs3/scfglib1.c
+++ b/src/sbbs3/scfglib1.c
@@ -39,16 +39,15 @@ BOOL allocerr(char* error, size_t maxerrlen, const char* fname, const char *item
 /****************************************************************************/
 BOOL read_node_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 {
-	char	path[MAX_PATH+1];
 	char	errstr[256];
 	FILE*	fp;
 	str_list_t	ini;
 	char	value[INI_MAX_VALUE_LEN];
 
 	const char* fname = "node.ini";
-	SAFEPRINTF2(path,"%s%s",cfg->node_dir,fname);
-	if((fp = fnopen(NULL, path, O_RDONLY)) == NULL) {
-		safe_snprintf(error, maxerrlen, "%d (%s) opening %s",errno,safe_strerror(errno, errstr, sizeof(errstr)),path);
+	SAFEPRINTF2(cfg->filename,"%s%s",cfg->node_dir,fname);
+	if((fp = fnopen(NULL, cfg->filename, O_RDONLY)) == NULL) {
+		safe_snprintf(error, maxerrlen, "%d (%s) opening %s",errno,safe_strerror(errno, errstr, sizeof(errstr)),cfg->filename);
 		return FALSE;
 	}
 	ini = iniReadFile(fp);
@@ -76,7 +75,6 @@ BOOL read_node_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 BOOL read_main_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 {
 	BOOL	result = FALSE;
-	char	path[MAX_PATH+1];
 	char	errstr[256];
 	FILE*	fp;
 	str_list_t	ini = NULL;
@@ -84,9 +82,9 @@ BOOL read_main_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 	str_list_t section;
 
 	const char* fname = "main.ini";
-	SAFEPRINTF2(path,"%s%s",cfg->ctrl_dir,fname);
-	if((fp = fnopen(NULL, path, O_RDONLY)) == NULL) {
-		safe_snprintf(error, maxerrlen, "%d (%s) opening %s",errno,safe_strerror(errno, errstr, sizeof(errstr)),path);
+	SAFEPRINTF2(cfg->filename,"%s%s",cfg->ctrl_dir,fname);
+	if((fp = fnopen(NULL, cfg->filename, O_RDONLY)) == NULL) {
+		safe_snprintf(error, maxerrlen, "%d (%s) opening %s",errno,safe_strerror(errno, errstr, sizeof(errstr)),cfg->filename);
 	} else {
 		ini = iniReadFile(fp);
 		fclose(fp);
@@ -332,16 +330,15 @@ BOOL read_main_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 /****************************************************************************/
 BOOL read_msgs_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 {
-	char	path[MAX_PATH+1];
 	char	errstr[256];
 	FILE*	fp;
 	str_list_t	ini;
 	char	value[INI_MAX_VALUE_LEN];
 
 	const char* fname = "msgs.ini";
-	SAFEPRINTF2(path,"%s%s",cfg->ctrl_dir,fname);
-	if((fp = fnopen(NULL, path, O_RDONLY)) == NULL) {
-		safe_snprintf(error, maxerrlen, "%d (%s) opening %s",errno,safe_strerror(errno, errstr, sizeof(errstr)),path);
+	SAFEPRINTF2(cfg->filename,"%s%s",cfg->ctrl_dir,fname);
+	if((fp = fnopen(NULL, cfg->filename, O_RDONLY)) == NULL) {
+		safe_snprintf(error, maxerrlen, "%d (%s) opening %s",errno,safe_strerror(errno, errstr, sizeof(errstr)),cfg->filename);
 		return FALSE;
 	}
 	ini = iniReadFile(fp);
@@ -678,7 +675,7 @@ void make_data_dirs(scfg_t* cfg)
 
 	for(int i = 0; i < cfg->total_dirs; i++) {
 		md(cfg->dir[i]->data_dir);
-		if(cfg->dir[i]->misc & DIR_FCHK) 
+		if(cfg->dir[i]->misc & DIR_FCHK)
 			md(cfg->dir[i]->path);
 	}
 }
diff --git a/src/sbbs3/scfglib2.c b/src/sbbs3/scfglib2.c
index 4102329ea424f11d75d7f501c529a6849a64013a..04113f30f2fc576d098efad5a8d75dc98c4b69b6 100644
--- a/src/sbbs3/scfglib2.c
+++ b/src/sbbs3/scfglib2.c
@@ -46,16 +46,15 @@ static void read_dir_defaults_cfg(scfg_t* cfg, str_list_t ini, dir_t* dir)
 /****************************************************************************/
 BOOL read_file_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 {
-	char	path[MAX_PATH+1];
 	char	errstr[256];
 	FILE*	fp;
 	str_list_t	ini;
 	char	value[INI_MAX_VALUE_LEN];
 
 	const char* fname = "file.ini";
-	SAFEPRINTF2(path,"%s%s",cfg->ctrl_dir,fname);
-	if((fp = fnopen(NULL, path, O_RDONLY)) == NULL) {
-		safe_snprintf(error, maxerrlen, "%d (%s) opening %s",errno,safe_strerror(errno, errstr, sizeof(errstr)),path);
+	SAFEPRINTF2(cfg->filename,"%s%s",cfg->ctrl_dir,fname);
+	if((fp = fnopen(NULL, cfg->filename, O_RDONLY)) == NULL) {
+		safe_snprintf(error, maxerrlen, "%d (%s) opening %s",errno,safe_strerror(errno, errstr, sizeof(errstr)),cfg->filename);
 		return FALSE;
 	}
 	ini = iniReadFile(fp);
@@ -364,16 +363,15 @@ BOOL read_file_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 /****************************************************************************/
 BOOL read_xtrn_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 {
-	char	path[MAX_PATH+1];
 	char	errstr[256];
 	FILE*	fp;
 	str_list_t	ini;
 	char	value[INI_MAX_VALUE_LEN];
 
 	const char* fname = "xtrn.ini";
-	SAFEPRINTF2(path,"%s%s",cfg->ctrl_dir,fname);
-	if((fp = fnopen(NULL, path, O_RDONLY)) == NULL) {
-		safe_snprintf(error, maxerrlen, "%d (%s) opening %s",errno,safe_strerror(errno, errstr, sizeof(errstr)),path);
+	SAFEPRINTF2(cfg->filename,"%s%s",cfg->ctrl_dir,fname);
+	if((fp = fnopen(NULL, cfg->filename, O_RDONLY)) == NULL) {
+		safe_snprintf(error, maxerrlen, "%d (%s) opening %s",errno,safe_strerror(errno, errstr, sizeof(errstr)),cfg->filename);
 		return FALSE;
 	}
 	ini = iniReadFile(fp);
@@ -576,16 +574,15 @@ BOOL read_xtrn_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 /****************************************************************************/
 BOOL read_chat_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 {
-	char	path[MAX_PATH+1];
 	char	errstr[256];
 	FILE*	fp;
 	str_list_t	ini;
 	char	value[INI_MAX_VALUE_LEN];
 
 	const char* fname = "chat.ini";
-	SAFEPRINTF2(path,"%s%s",cfg->ctrl_dir,fname);
-	if((fp = fnopen(NULL, path, O_RDONLY)) == NULL) {
-		safe_snprintf(error, maxerrlen, "%d (%s) opening %s",errno,safe_strerror(errno, errstr, sizeof(errstr)),path);
+	SAFEPRINTF2(cfg->filename,"%s%s",cfg->ctrl_dir,fname);
+	if((fp = fnopen(NULL, cfg->filename, O_RDONLY)) == NULL) {
+		safe_snprintf(error, maxerrlen, "%d (%s) opening %s",errno,safe_strerror(errno, errstr, sizeof(errstr)),cfg->filename);
 		return FALSE;
 	}
 	ini = iniReadFile(fp);
@@ -746,7 +743,7 @@ uint32_t aftou32(const char *str)
 		ch=toupper(str[c]);
 		if(ch>='A' && ch<='Z')
 			l|=FLAG(ch);
-		c++; 
+		c++;
 	}
 	return(l);
 }
@@ -762,7 +759,7 @@ char *u32toaf(uint32_t l,char *str)
 		if(l & (1 << c))
 			str[c]='A'+c;
 		else str[c]=' ';
-		c++; 
+		c++;
 	}
 	str[c]=0;
 	return(str);
@@ -832,12 +829,12 @@ uint attrstr(char *str)
 				break;
 			case '6':	/* Cyan Background */
 				atr=(uchar)((atr&0x8f)|BG_CYAN);
-				break; 
+				break;
 			case '7':	/* White Background */
 				atr=(uchar)((atr&0x8f)|BG_LIGHTGRAY);
 				break;
 			}
-		l++; 
+		l++;
 	}
 	return(atr);
 }
diff --git a/src/sbbs3/umonitor/umonitor.c b/src/sbbs3/umonitor/umonitor.c
index 19bffd961e0a54fcbdb2e0b88ee7f795a844016e..e8b0ef8d0069655d2a20604dc033c72a141be256 100644
--- a/src/sbbs3/umonitor/umonitor.c
+++ b/src/sbbs3/umonitor/umonitor.c
@@ -869,7 +869,10 @@ int main(int argc, char** argv)  {
 	if(fp!=NULL)
 		fclose(fp);
 
-	chdir(bbs_startup.ctrl_dir);
+	if(chdir(bbs_startup.ctrl_dir) != 0) {
+		printf("Error %d changing directory to: %s\n", errno, bbs_startup.ctrl_dir);
+		exit(1);
+	}
 
 	/* Read .cfg files here */
 	memset(&cfg,0,sizeof(cfg));
diff --git a/src/sbbs3/websrvr.c b/src/sbbs3/websrvr.c
index 2bcf1e677768258102020a9eebcd1ed473d15eda..6b7b33e1d5318c9a9c0c94f844bc1266e6d8c8a7 100644
--- a/src/sbbs3/websrvr.c
+++ b/src/sbbs3/websrvr.c
@@ -2392,7 +2392,7 @@ static void js_add_request_property(http_session_t * session, char *key, char *v
 			js_str=JS_NewStringCopyN(session->js_cx, value, len);
 		else
 			js_str=JS_NewStringCopyZ(session->js_cx, value);
-	
+
 		if(js_str == NULL)
 			return;
 
@@ -7328,7 +7328,7 @@ void web_server(void* arg)
 				lprintf(LOG_NOTICE, "%04d New active client highwater mark: %lu"
 					,client_socket, client_highwater);
 			}
-			if(startup->max_clients && protected_uint32_value(active_clients)>=startup->max_clients) {
+			if(startup->max_clients && count>=startup->max_clients) {
 				lprintf(LOG_WARNING,"%04d [%s] !MAXIMUM CLIENTS (%d) reached, access denied"
 					,client_socket, host_ip, startup->max_clients);
 				if (!len_503)
diff --git a/src/uifc/uifcx.c b/src/uifc/uifcx.c
index 323ccca1c31e090e894e5f05fe20955b38b782ab..fb4c441552c785a277cc7316a7a0664e589fa2ad 100644
--- a/src/uifc/uifcx.c
+++ b/src/uifc/uifcx.c
@@ -206,6 +206,10 @@ int ulist(uifc_winmode_t mode, int left, int top, int width, int *cur, int *bar
     int optnumlen;
     int yesno=0;
     int lines;
+    int	tmpcur=0;
+
+	if(cur == NULL)
+		cur = &tmpcur;
 
     for(opts=0;opts<MAX_OPTS;opts++)
     	if(option[opts]==NULL || option[opts][0]==0)
@@ -230,7 +234,8 @@ int ulist(uifc_winmode_t mode, int left, int top, int width, int *cur, int *bar
             yesno=1;
             printf("%s? ",title);
         } else {
-            printf("\n[%s]\n",title);
+			if(title != NULL)
+				printf("\n[%s]\n",title);
             lines=2;
             for(i=0;i<opts;i++) {
                 printf("%*d: %s\n",optnumlen,i+1,option[i]);
diff --git a/src/xpdev/ini_file.c b/src/xpdev/ini_file.c
index 74533b0bb9cd02e1d0137feefe453b34b489e8fc..ee838392c74fe249a97cb1792f47a5a3183cb051 100644
--- a/src/xpdev/ini_file.c
+++ b/src/xpdev/ini_file.c
@@ -425,6 +425,8 @@ BOOL iniRemoveValue(str_list_t* list, const char* section, const char* key)
 	if(vp==NULL)
 		return(FALSE);
 
+	while(*vp != '\0' && isspace(*(vp - 1)))
+		--vp;
 	*vp=0;
 	return(TRUE);
 }
@@ -794,9 +796,11 @@ char* iniSetBytes(str_list_t* list, const char* section, const char* key, uint u
 char* iniSetDuration(str_list_t* list, const char* section, const char* key
 					,double value, ini_style_t* style)
 {
-	char	str[INI_MAX_VALUE_LEN];
+	char	str[INI_MAX_VALUE_LEN] = "0";
 
-	return iniSetString(list, section, key, duration_to_str(value, str, sizeof(str)), style);
+	if(value)
+		duration_to_str(value, str, sizeof(str));
+	return iniSetString(list, section, key, str, style);
 }
 
 
diff --git a/src/xpdev/str_list.c b/src/xpdev/str_list.c
index 51e37b03dbd450c371742f3cf6b1ac39e5d36d1f..e91cc3162a561c3674eb404a758d345f4b609278 100644
--- a/src/xpdev/str_list.c
+++ b/src/xpdev/str_list.c
@@ -590,8 +590,7 @@ int strListCmp(str_list_t list1, str_list_t list2)
 	strListSortAlphaCase(l1);
 	strListSortAlphaCase(l2);
 
-	for(; *l1; l1++) {
-		l2++;
+	for(; *l1; l1++, l2++) {
 		if(*l2==NULL) {
 			ret=1;
 			goto early_return;
@@ -602,7 +601,6 @@ int strListCmp(str_list_t list1, str_list_t list2)
 			goto early_return;
 		}
 	}
-	l2++;
 	if(*l2==NULL)
 		ret=0;
 	else