diff --git a/ctrl/sbbs.ini b/ctrl/sbbs.ini
index f123bf27d70b185f9bdf8063246a638106050e28..25e3034eafeb588dfdbcc2f71661a93521d5c393 100644
--- a/ctrl/sbbs.ini
+++ b/ctrl/sbbs.ini
@@ -230,6 +230,8 @@ Options=INDEX_FILE | HTML_INDEX_FILE | ALLOW_QWK
 	AutoStart=true
 	Interface=
 	Port=80
+	TLSInterface=
+	TLSPort=443
         MaxClients=150
 	RootDirectory=../web/root
 	ErrorDirectory=error
diff --git a/ctrl/sockopts.ini b/ctrl/sockopts.ini
index a2f65b882cb10ed6166e47b3555360844df4e8b5..96108f9d4f11b59a19fa0555a80eac075e5c0a84 100644
--- a/ctrl/sockopts.ini
+++ b/ctrl/sockopts.ini
@@ -28,6 +28,7 @@
 ; Global socket options set here, in root section
 SNDBUF = 8192
 RCVBUF = 8192
+IPV6_V6ONLY = 1
 
 ; TCP-specific options set here
 [tcp]
diff --git a/exec/load/sbbsdefs.js b/exec/load/sbbsdefs.js
index c5a3048e3ff34e2190548f56b6194a96f71e0e0d..13ffa8a4cda80ff09848ff6c7b90dac461002ef3 100644
--- a/exec/load/sbbsdefs.js
+++ b/exec/load/sbbsdefs.js
@@ -771,12 +771,13 @@ var LEN_FCDT			=9;		/* 9 digits for file credit values				*/
 var LEN_TITLE			=70;	/* Message title								*/
 var LEN_MAIN_CMD		=34;	/* Storage in user.dat for custom commands		*/
 var LEN_XFER_CMD		=40;													
-var LEN_SCAN_CMD		=40;													
-var LEN_MAIL_CMD		=40;													
-var LEN_CID 			=25;	/* Caller ID (phone number) 					*/
+var LEN_SCAN_CMD		=35;													
+var LEN_IPADDR			=45;													
+var LEN_CID 			=45;	/* Caller ID (phone number or IP address) 		*/
 var LEN_ARSTR			=40;	/* Max length of Access Requirement string		*/
 var LEN_CHATACTCMD		=9;		/* Chat action command							*/
-var LEN_CHATACTOUT		=65;	/* Chat action output string					*/								  /************************************************/
+var LEN_CHATACTOUT		=65;	/* Chat action output string					*/
+								/************************************************/
 						
 								
 /********************************************/
@@ -833,8 +834,8 @@ var U_CURXTRN		=U_CURSUB+16; 	/* Current xtrn (internal code) */
 var U_MAIN_CMD		=U_CURXTRN+8+2; /* unused */
 var U_XFER_CMD		=U_MAIN_CMD+LEN_MAIN_CMD; 		/* unused */
 var U_SCAN_CMD		=U_XFER_CMD+LEN_XFER_CMD+2;  	/* unused */
-var U_MAIL_CMD		=U_SCAN_CMD+LEN_SCAN_CMD; 		/* unused */
-var U_FREECDT		=U_MAIL_CMD+LEN_MAIL_CMD+2; 
+var U_IPADDR		=U_SCAN_CMD+LEN_SCAN_CMD; 		/* unused */
+var U_FREECDT		=U_IPADDR+LEN_IPADDR+2; 
 var U_FLAGS3		=U_FREECDT+10; 	/* Flag set #3 */
 var U_FLAGS4		=U_FLAGS3+8; 	/* Flag set #4 */
 var U_XEDIT 		=U_FLAGS4+8; 	/* External editor (code  */
diff --git a/src/sbbs3/answer.cpp b/src/sbbs3/answer.cpp
index 706a186d857cf901f922e388a993856602b17c52..3912e8b96ab3a15b39e405711ec10993b3b95a21 100644
--- a/src/sbbs3/answer.cpp
+++ b/src/sbbs3/answer.cpp
@@ -52,7 +52,7 @@ bool sbbs_t::answer()
 	useron.number=0;
 	answertime=logontime=starttime=now=time(NULL);
 	/* Caller ID is IP address */
-	SAFECOPY(cid,inet_ntoa(client_addr.sin_addr));
+	SAFECOPY(cid,client_ipaddr);
 
 	memset(&tm,0,sizeof(tm));
     localtime_r(&now,&tm); 
@@ -349,7 +349,7 @@ bool sbbs_t::answer()
 	/* AutoLogon via IP or Caller ID here */
 	if(!useron.number && !(sys_status&SS_RLOGIN)
 		&& (startup->options&BBS_OPT_AUTO_LOGON) && cid[0]) {
-		useron.number=userdatdupe(0, U_NOTE, LEN_NOTE, cid);
+		useron.number=userdatdupe(0, U_IPADDR, LEN_IPADDR, cid);
 		if(useron.number) {
 			getuserdat(&cfg, &useron);
 			if(!(useron.misc&AUTOLOGON) || !(useron.exempt&FLAG('V')))
@@ -436,8 +436,8 @@ bool sbbs_t::answer()
 
 	/* Save the IP to the user's note */
 	if(cid[0]) {
-		SAFECOPY(useron.note,cid);
-		putuserrec(&cfg,useron.number,U_NOTE,LEN_NOTE,useron.note);
+		SAFECOPY(useron.ipaddr,cid);
+		putuserrec(&cfg,useron.number,U_IPADDR,LEN_IPADDR,useron.ipaddr);
 	}
 
 	/* Save host name to the user's computer description */
diff --git a/src/sbbs3/atcodes.cpp b/src/sbbs3/atcodes.cpp
index 3f3bd27fbd008643bb0bb3fe851fd2dd49faf3fe..cecbfe452e4e6b9dd6f4044486fa5e3e6cbc6b23 100644
--- a/src/sbbs3/atcodes.cpp
+++ b/src/sbbs3/atcodes.cpp
@@ -636,11 +636,8 @@ const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen)
 	if(!strcmp(sp,"CID") || !strcmp(sp,"IP"))
 		return(cid);
 
-	if(!strcmp(sp,"LOCAL-IP")) {
-		struct in_addr in_addr;
-		in_addr.s_addr=local_addr;
-		return(inet_ntoa(in_addr));
-	}
+	if(!strcmp(sp,"LOCAL-IP"))
+		return(local_addr);
 
 	if(!strcmp(sp,"CRLF"))
 		return("\r\n");
diff --git a/src/sbbs3/chk_ar.cpp b/src/sbbs3/chk_ar.cpp
index 8ecd32c92d25e0fb5ef30afd7ee43fbbe6733207..807c09faec49af99e0664dad66825c0df8a228b7 100644
--- a/src/sbbs3/chk_ar.cpp
+++ b/src/sbbs3/chk_ar.cpp
@@ -631,7 +631,7 @@ bool sbbs_t::ar_exp(const uchar **ptrptr, user_t* user, client_t* client)
 				if(client!=NULL)
 					p=client->addr;
 				else
-					p=user->note;
+					p=user->ipaddr;
 				if(!findstr_in_string(p,(char*)*ptrptr))
 					result=_not;
 				else
diff --git a/src/sbbs3/client.h b/src/sbbs3/client.h
index 167b559065a1b835715f1d135688685324ac966c..fab1f6e89b6d25f7db9553c123e8cefa2e6ab093 100644
--- a/src/sbbs3/client.h
+++ b/src/sbbs3/client.h
@@ -39,18 +39,18 @@
 #define _CLIENT_H
 
 #include "gen_defs.h"	/* WORD, DWORD */
+#include "sockwrap.h"	/* INET6_ADDRSTRLEN */
 #include <time.h>		/* time_t */
 
 /* Used for sbbsctrl->client window */
 typedef struct {
 	size_t		size;		/* size of this struct */
-	char		addr[16];	/* IP address */
-	char		host[64];	/* host name */
+	char		addr[INET6_ADDRSTRLEN];	/* IP address */
+	char		host[256];	/* host name */
 	WORD		port;		/* TCP port number */
 	time32_t	time;		/* connect time */
 	const char*	protocol;	/* protocol description */
 	const char*	user;		/* user name */
-	char		pad[32];	/* padding for future expansion */
 } client_t;
 
 /* Used for ctrl/client.dab */
diff --git a/src/sbbs3/cmdshell.h b/src/sbbs3/cmdshell.h
index 78cfffd142ceb447f61e3ecec5f22a613b931362..6267b81dfb122cc73690a59afc55d63c1b72ab4d 100644
--- a/src/sbbs3/cmdshell.h
+++ b/src/sbbs3/cmdshell.h
@@ -596,6 +596,7 @@ enum {
 	,USER_STRING_MODEM
 	,USER_STRING_COMMENT
 	,USER_STRING_NETMAIL
+	,USER_STRING_IPADDR
 
 	};
 
diff --git a/src/sbbs3/ctrl/FtpCfgDlgUnit.cpp b/src/sbbs3/ctrl/FtpCfgDlgUnit.cpp
index e5e5d5e84fceac6161ecfd97cd0b4670bd81fbd6..ff75f6a8e5ca8c6837fff1ddbc361257ec13aa01 100644
--- a/src/sbbs3/ctrl/FtpCfgDlgUnit.cpp
+++ b/src/sbbs3/ctrl/FtpCfgDlgUnit.cpp
@@ -55,25 +55,25 @@ void __fastcall TFtpCfgDlg::FormShow(TObject *Sender)
 {
     char str[128];
 
-    if(MainForm->ftp_startup.interface_addr==0)
+    if(MainForm->ftp_startup.outgoing4.s_addr==0)
         NetworkInterfaceEdit->Text="<ANY>";
     else {
         sprintf(str,"%d.%d.%d.%d"
-            ,(MainForm->ftp_startup.interface_addr>>24)&0xff
-            ,(MainForm->ftp_startup.interface_addr>>16)&0xff
-            ,(MainForm->ftp_startup.interface_addr>>8)&0xff
-            ,MainForm->ftp_startup.interface_addr&0xff
+            ,(MainForm->ftp_startup.outgoing4.s_addr>>24)&0xff
+            ,(MainForm->ftp_startup.outgoing4.s_addr>>16)&0xff
+            ,(MainForm->ftp_startup.outgoing4.s_addr>>8)&0xff
+            ,MainForm->ftp_startup.outgoing4.s_addr&0xff
         );
         NetworkInterfaceEdit->Text=AnsiString(str);
     }
-    if(MainForm->ftp_startup.pasv_ip_addr==0)
+    if(MainForm->ftp_startup.pasv_ip_addr.s_addr==0)
         PasvIpAddrEdit->Text="<unspecified>";
     else {
         sprintf(str,"%d.%d.%d.%d"
-            ,(MainForm->ftp_startup.pasv_ip_addr>>24)&0xff
-            ,(MainForm->ftp_startup.pasv_ip_addr>>16)&0xff
-            ,(MainForm->ftp_startup.pasv_ip_addr>>8)&0xff
-            ,MainForm->ftp_startup.pasv_ip_addr&0xff
+            ,(MainForm->ftp_startup.pasv_ip_addr.s_addr>>24)&0xff
+            ,(MainForm->ftp_startup.pasv_ip_addr.s_addr>>16)&0xff
+            ,(MainForm->ftp_startup.pasv_ip_addr.s_addr>>8)&0xff
+            ,MainForm->ftp_startup.pasv_ip_addr.s_addr&0xff
         );
         PasvIpAddrEdit->Text=AnsiString(str);
     }
@@ -135,9 +135,9 @@ void __fastcall TFtpCfgDlg::OKBtnClick(TObject *Sender)
         while(*p && *p!='.') p++;
         if(*p=='.') p++;
         addr|=atoi(p);
-        MainForm->ftp_startup.interface_addr=addr;
+        MainForm->ftp_startup.outgoing4.s_addr=addr;
     } else
-        MainForm->ftp_startup.interface_addr=0;
+        MainForm->ftp_startup.outgoing4.s_addr=0;
     SAFECOPY(str,PasvIpAddrEdit->Text.c_str());
     p=str;
     while(*p && *p<=' ') p++;
@@ -152,9 +152,9 @@ void __fastcall TFtpCfgDlg::OKBtnClick(TObject *Sender)
         while(*p && *p!='.') p++;
         if(*p=='.') p++;
         addr|=atoi(p);
-        MainForm->ftp_startup.pasv_ip_addr=addr;
+        MainForm->ftp_startup.pasv_ip_addr.s_addr=addr;
     } else
-        MainForm->ftp_startup.pasv_ip_addr=0;
+        MainForm->ftp_startup.pasv_ip_addr.s_addr=0;
 
     MainForm->ftp_startup.max_clients=MaxClientsEdit->Text.ToIntDef(FTP_DEFAULT_MAX_CLIENTS);
     MainForm->ftp_startup.max_inactivity=MaxInactivityEdit->Text.ToIntDef(FTP_DEFAULT_MAX_INACTIVITY);
diff --git a/src/sbbs3/ctrl/LoginAttemptsFormUnit.cpp b/src/sbbs3/ctrl/LoginAttemptsFormUnit.cpp
index 298be978a5e271d03624b7dbd3880e391c9f2af6..1aeddfbcfbd37c634c9bd069a97a5a763f54af90 100644
--- a/src/sbbs3/ctrl/LoginAttemptsFormUnit.cpp
+++ b/src/sbbs3/ctrl/LoginAttemptsFormUnit.cpp
@@ -78,7 +78,7 @@ void __fastcall TLoginAttemptsForm::FillListView(TObject *Sender)
         Item->Caption=AnsiString(attempt->count-attempt->dupes);
         Item->Data=(void*)attempt->time;
         Item->SubItems->Add(attempt->dupes);        
-        Item->SubItems->Add(inet_ntoa(attempt->addr));
+        Item->SubItems->Add("TODO: Add addresses");
         Item->SubItems->Add(attempt->prot);
         Item->SubItems->Add(attempt->user);
         Item->SubItems->Add(attempt->pass);
diff --git a/src/sbbs3/ctrl/MailCfgDlgUnit.cpp b/src/sbbs3/ctrl/MailCfgDlgUnit.cpp
index 123ebffa64b48c86a1b7fc3f18442e7df166dc0e..88811049ebefc19ccef5d926526fc67224173d6f 100644
--- a/src/sbbs3/ctrl/MailCfgDlgUnit.cpp
+++ b/src/sbbs3/ctrl/MailCfgDlgUnit.cpp
@@ -75,14 +75,14 @@ void __fastcall TMailCfgDlg::FormShow(TObject *Sender)
 {
     char str[128];
 
-    if(MainForm->mail_startup.interface_addr==0)
+    if(MainForm->mail_startup.outgoing4.s_addr==0)
         NetworkInterfaceEdit->Text="<ANY>";
     else {
         sprintf(str,"%d.%d.%d.%d"
-            ,(MainForm->mail_startup.interface_addr>>24)&0xff
-            ,(MainForm->mail_startup.interface_addr>>16)&0xff
-            ,(MainForm->mail_startup.interface_addr>>8)&0xff
-            ,MainForm->mail_startup.interface_addr&0xff
+            ,(MainForm->mail_startup.outgoing4.s_addr>>24)&0xff
+            ,(MainForm->mail_startup.outgoing4.s_addr>>16)&0xff
+            ,(MainForm->mail_startup.outgoing4.s_addr>>8)&0xff
+            ,MainForm->mail_startup.outgoing4.s_addr&0xff
         );
         NetworkInterfaceEdit->Text=AnsiString(str);
     }
@@ -233,9 +233,9 @@ void __fastcall TMailCfgDlg::OKBtnClick(TObject *Sender)
         while(*p && *p!='.') p++;
         if(*p=='.') p++;
         addr|=atoi(p);
-        MainForm->mail_startup.interface_addr=addr;
+        MainForm->mail_startup.outgoing4.s_addr=addr;
     } else
-        MainForm->mail_startup.interface_addr=0;
+        MainForm->mail_startup.outgoing4.s_addr=0;
 
 	MainForm->mail_startup.smtp_port=SMTPPortEdit->Text.ToIntDef(IPPORT_SMTP);
    	MainForm->mail_startup.submission_port=SubPortEdit->Text.ToIntDef(IPPORT_SUBMISSION);
diff --git a/src/sbbs3/ctrl/MainFormUnit.cpp b/src/sbbs3/ctrl/MainFormUnit.cpp
index 7dda8be4357f8034de3b5d7714d0089205b1afe9..f4514a1c4776996784fce7b02b508187cd7a5acc 100644
--- a/src/sbbs3/ctrl/MainFormUnit.cpp
+++ b/src/sbbs3/ctrl/MainFormUnit.cpp
@@ -845,9 +845,7 @@ __fastcall TMainForm::TMainForm(TComponent* Owner)
     bbs_startup.last_node=4;
 	bbs_startup.options=BBS_OPT_XTRN_MINIMIZED|BBS_OPT_SYSOP_AVAILABLE;
 	bbs_startup.telnet_port=IPPORT_TELNET;
-    bbs_startup.telnet_interface=INADDR_ANY;
     bbs_startup.rlogin_port=513;
-    bbs_startup.rlogin_interface=INADDR_ANY;
 	bbs_startup.lputs=lputs;
     bbs_startup.event_lputs=lputs;
     bbs_startup.errormsg=errormsg;
@@ -867,7 +865,6 @@ __fastcall TMainForm::TMainForm(TComponent* Owner)
     mail_startup.smtp_port=IPPORT_SMTP;
     mail_startup.relay_port=IPPORT_SMTP;
     mail_startup.pop3_port=110;
-    mail_startup.interface_addr=INADDR_ANY;
 	mail_startup.lputs=lputs;
     mail_startup.errormsg=errormsg;
     mail_startup.status=mail_status;
@@ -890,7 +887,6 @@ __fastcall TMainForm::TMainForm(TComponent* Owner)
     ftp_startup.size=sizeof(ftp_startup);
     ftp_startup.cbdata=&ftp_log_list;
     ftp_startup.port=IPPORT_FTP;
-    ftp_startup.interface_addr=INADDR_ANY;
 	ftp_startup.lputs=lputs;
     ftp_startup.errormsg=errormsg;
     ftp_startup.status=ftp_status;
@@ -927,7 +923,6 @@ __fastcall TMainForm::TMainForm(TComponent* Owner)
     memset(&services_startup,0,sizeof(services_startup));
     services_startup.size=sizeof(services_startup);
     services_startup.cbdata=&services_log_list;
-    services_startup.interface_addr=INADDR_ANY;
     services_startup.lputs=lputs;
     services_startup.errormsg=errormsg;
     services_startup.status=services_status;
@@ -2018,10 +2013,12 @@ void __fastcall TMainForm::StartupTimerTick(TObject *Sender)
         if(Registry->ValueExists("JS_YieldInterval"))
             global.js.yield_interval=Registry->ReadInteger("JS_YieldInterval");
 
+/*
         if(Registry->ValueExists("TelnetInterface"))
             bbs_startup.telnet_interface=Registry->ReadInteger("TelnetInterface");
         if(Registry->ValueExists("RLoginInterface"))
             bbs_startup.rlogin_interface=Registry->ReadInteger("RLoginInterface");
+*/
 
         if(Registry->ValueExists("TelnetPort"))
             bbs_startup.telnet_port=Registry->ReadInteger("TelnetPort");
@@ -2060,8 +2057,10 @@ void __fastcall TMainForm::StartupTimerTick(TObject *Sender)
         if(Registry->ValueExists("MailMaxInactivity"))
             mail_startup.max_inactivity=Registry->ReadInteger("MailMaxInactivity");
 
+/*
         if(Registry->ValueExists("MailInterface"))
             mail_startup.interface_addr=Registry->ReadInteger("MailInterface");
+*/
 
         if(Registry->ValueExists("MailMaxDeliveryAttempts"))
             mail_startup.max_delivery_attempts
@@ -2140,8 +2139,10 @@ void __fastcall TMainForm::StartupTimerTick(TObject *Sender)
         if(Registry->ValueExists("FtpQwkTimeout"))
             ftp_startup.qwk_timeout=Registry->ReadInteger("FtpQwkTimeout");
 
+/*
         if(Registry->ValueExists("FtpInterface"))
             ftp_startup.interface_addr=Registry->ReadInteger("FtpInterface");
+*/
 
         if(Registry->ValueExists("FtpPort"))
             ftp_startup.port=Registry->ReadInteger("FtpPort");
@@ -2173,9 +2174,11 @@ void __fastcall TMainForm::StartupTimerTick(TObject *Sender)
         if(Registry->ValueExists("FtpOptions"))
             ftp_startup.options=Registry->ReadInteger("FtpOptions");
 
+/*
         if(Registry->ValueExists("ServicesInterface"))
             services_startup.interface_addr
                 =Registry->ReadInteger("ServicesInterface");
+*/
 
         if(Registry->ValueExists("ServicesAnswerSound"))
             SAFECOPY(services_startup.answer_sound
diff --git a/src/sbbs3/ctrl/ServicesCfgDlgUnit.cpp b/src/sbbs3/ctrl/ServicesCfgDlgUnit.cpp
index c389dfe1a1825cd9bff859b0436b09088a3056bc..b8e6e96f5c9e5e10256edfb65d18aa3ea89a1154 100644
--- a/src/sbbs3/ctrl/ServicesCfgDlgUnit.cpp
+++ b/src/sbbs3/ctrl/ServicesCfgDlgUnit.cpp
@@ -23,15 +23,17 @@ __fastcall TServicesCfgDlg::TServicesCfgDlg(TComponent* Owner)
 void __fastcall TServicesCfgDlg::FormShow(TObject *Sender)
 {
     char str[128];
-
-    if(MainForm->services_startup.interface_addr==0)
+/*
+TODO: This is broken and stuff.
+*/
+    if(MainForm->services_startup.outgoing4.s_addr==0)
         NetworkInterfaceEdit->Text="<ANY>";
     else {
         sprintf(str,"%d.%d.%d.%d"
-            ,(MainForm->services_startup.interface_addr>>24)&0xff
-            ,(MainForm->services_startup.interface_addr>>16)&0xff
-            ,(MainForm->services_startup.interface_addr>>8)&0xff
-            ,MainForm->services_startup.interface_addr&0xff
+            ,(MainForm->services_startup.outgoing4.s_addr>>24)&0xff
+            ,(MainForm->services_startup.outgoing4.s_addr>>16)&0xff
+            ,(MainForm->services_startup.outgoing4.s_addr>>8)&0xff
+            ,MainForm->services_startup.outgoing4.s_addr&0xff
         );
         NetworkInterfaceEdit->Text=AnsiString(str);
     }
@@ -99,9 +101,9 @@ void __fastcall TServicesCfgDlg::OKButtonClick(TObject *Sender)
         while(*p && *p!='.') p++;
         if(*p=='.') p++;
         addr|=atoi(p);
-        MainForm->services_startup.interface_addr=addr;
+        MainForm->services_startup.outgoing4.s_addr=addr;
     } else
-        MainForm->services_startup.interface_addr=0;
+        MainForm->services_startup.outgoing4.s_addr=0;
     MainForm->ServicesAutoStart=AutoStartCheckBox->Checked;
 
 
diff --git a/src/sbbs3/ctrl/TelnetCfgDlgUnit.cpp b/src/sbbs3/ctrl/TelnetCfgDlgUnit.cpp
index 73e7f713106c6f47a4929c06cdc0fae09054b796..7ab481fdb4397fab4d9af3ed20cd1299da155a7d 100644
--- a/src/sbbs3/ctrl/TelnetCfgDlgUnit.cpp
+++ b/src/sbbs3/ctrl/TelnetCfgDlgUnit.cpp
@@ -55,37 +55,37 @@ void __fastcall TTelnetCfgDlg::FormShow(TObject *Sender)
 {
     char str[128];
 
-    if(MainForm->bbs_startup.telnet_interface==0)
+    if(MainForm->bbs_startup.outgoing4.s_addr==0)
         TelnetInterfaceEdit->Text="<ANY>";
     else {
         sprintf(str,"%d.%d.%d.%d"
-            ,(MainForm->bbs_startup.telnet_interface>>24)&0xff
-            ,(MainForm->bbs_startup.telnet_interface>>16)&0xff
-            ,(MainForm->bbs_startup.telnet_interface>>8)&0xff
-            ,MainForm->bbs_startup.telnet_interface&0xff
+            ,(MainForm->bbs_startup.outgoing4.s_addr>>24)&0xff
+            ,(MainForm->bbs_startup.outgoing4.s_addr>>16)&0xff
+            ,(MainForm->bbs_startup.outgoing4.s_addr>>8)&0xff
+            ,MainForm->bbs_startup.outgoing4.s_addr&0xff
         );
         TelnetInterfaceEdit->Text=AnsiString(str);
     }
-    if(MainForm->bbs_startup.rlogin_interface==0)
+    if(MainForm->bbs_startup.outgoing4.s_addr==0)
         RLoginInterfaceEdit->Text="<ANY>";
     else {
         sprintf(str,"%d.%d.%d.%d"
-            ,(MainForm->bbs_startup.rlogin_interface>>24)&0xff
-            ,(MainForm->bbs_startup.rlogin_interface>>16)&0xff
-            ,(MainForm->bbs_startup.rlogin_interface>>8)&0xff
-            ,MainForm->bbs_startup.rlogin_interface&0xff
+            ,(MainForm->bbs_startup.outgoing4.s_addr>>24)&0xff
+            ,(MainForm->bbs_startup.outgoing4.s_addr>>16)&0xff
+            ,(MainForm->bbs_startup.outgoing4.s_addr>>8)&0xff
+            ,MainForm->bbs_startup.outgoing4.s_addr&0xff
         );
         RLoginInterfaceEdit->Text=AnsiString(str);
     }
 
-    if(MainForm->bbs_startup.ssh_interface==0)
+    if(MainForm->bbs_startup.outgoing4.s_addr==0)
         SshInterfaceEdit->Text="<ANY>";
     else {
         sprintf(str,"%d.%d.%d.%d"
-            ,(MainForm->bbs_startup.ssh_interface>>24)&0xff
-            ,(MainForm->bbs_startup.ssh_interface>>16)&0xff
-            ,(MainForm->bbs_startup.ssh_interface>>8)&0xff
-            ,MainForm->bbs_startup.ssh_interface&0xff
+            ,(MainForm->bbs_startup.outgoing4.s_addr>>24)&0xff
+            ,(MainForm->bbs_startup.outgoing4.s_addr>>16)&0xff
+            ,(MainForm->bbs_startup.outgoing4.s_addr>>8)&0xff
+            ,MainForm->bbs_startup.outgoing4.s_addr&0xff
         );
         SshInterfaceEdit->Text=AnsiString(str);
     }
@@ -148,9 +148,9 @@ void __fastcall TTelnetCfgDlg::OKBtnClick(TObject *Sender)
         while(*p && *p!='.') p++;
         if(*p=='.') p++;
         addr|=atoi(p);
-        MainForm->bbs_startup.telnet_interface=addr;
+        MainForm->bbs_startup.outgoing4.s_addr=addr;
     } else
-        MainForm->bbs_startup.telnet_interface=0;
+        MainForm->bbs_startup.outgoing4.s_addr=0;
 
     SAFECOPY(str,RLoginInterfaceEdit->Text.c_str());
     p=str;
@@ -166,9 +166,9 @@ void __fastcall TTelnetCfgDlg::OKBtnClick(TObject *Sender)
         while(*p && *p!='.') p++;
         if(*p=='.') p++;
         addr|=atoi(p);
-        MainForm->bbs_startup.rlogin_interface=addr;
+        MainForm->bbs_startup.outgoing4.s_addr=addr;
     } else
-        MainForm->bbs_startup.rlogin_interface=0;
+        MainForm->bbs_startup.outgoing4.s_addr=0;
 
     SAFECOPY(str,SshInterfaceEdit->Text.c_str());
     p=str;
@@ -184,9 +184,9 @@ void __fastcall TTelnetCfgDlg::OKBtnClick(TObject *Sender)
         while(*p && *p!='.') p++;
         if(*p=='.') p++;
         addr|=atoi(p);
-        MainForm->bbs_startup.ssh_interface=addr;
+        MainForm->bbs_startup.outgoing4.s_addr=addr;
     } else
-        MainForm->bbs_startup.ssh_interface=0;
+        MainForm->bbs_startup.outgoing4.s_addr=0;
 
     MainForm->bbs_startup.telnet_port=TelnetPortEdit->Text.ToIntDef(23);
     MainForm->bbs_startup.rlogin_port=RLoginPortEdit->Text.ToIntDef(513);
diff --git a/src/sbbs3/ctrl/WebCfgDlgUnit.cpp b/src/sbbs3/ctrl/WebCfgDlgUnit.cpp
index 6524d29b451a9d1d3381b121ecd4e814d9c1f44d..3578358d30e4e647a9f9b5a7458547b511c1fd5b 100644
--- a/src/sbbs3/ctrl/WebCfgDlgUnit.cpp
+++ b/src/sbbs3/ctrl/WebCfgDlgUnit.cpp
@@ -58,14 +58,14 @@ void __fastcall TWebCfgDlg::FormShow(TObject *Sender)
     char str[128];
     char** p;
 
-    if(MainForm->web_startup.interface_addr==0)
+    if(MainForm->web_startup.outgoing4.s_addr==0)
         NetworkInterfaceEdit->Text="<ANY>";
     else {
         sprintf(str,"%d.%d.%d.%d"
-            ,(MainForm->web_startup.interface_addr>>24)&0xff
-            ,(MainForm->web_startup.interface_addr>>16)&0xff
-            ,(MainForm->web_startup.interface_addr>>8)&0xff
-            ,MainForm->web_startup.interface_addr&0xff
+            ,(MainForm->web_startup.outgoing4.s_addr>>24)&0xff
+            ,(MainForm->web_startup.outgoing4.s_addr>>16)&0xff
+            ,(MainForm->web_startup.outgoing4.s_addr>>8)&0xff
+            ,MainForm->web_startup.outgoing4.s_addr&0xff
         );
         NetworkInterfaceEdit->Text=AnsiString(str);
     }
@@ -140,10 +140,10 @@ void __fastcall TWebCfgDlg::OKBtnClick(TObject *Sender)
         while(*p && *p!='.') p++;
         if(*p=='.') p++;
         addr|=atoi(p);
-        MainForm->web_startup.interface_addr=addr;
+        MainForm->web_startup.outgoing4.s_addr=addr;
     } else
-        MainForm->web_startup.interface_addr=0;
-    MainForm->web_startup.max_clients=MaxClientsEdit->Text.ToIntDef(0);
+        MainForm->web_startup.outgoing4.s_addr=0;
+    MainForm->web_startup.max_clients=MaxClientsEdit->Text.ToIntDef(10);
     MainForm->web_startup.max_inactivity=MaxInactivityEdit->Text.ToIntDef(WEB_DEFAULT_MAX_INACTIVITY);
     MainForm->web_startup.port=PortEdit->Text.ToIntDef(IPPORT_HTTP);
     MainForm->WebAutoStart=AutoStartCheckBox->Checked;
diff --git a/src/sbbs3/exec.cpp b/src/sbbs3/exec.cpp
index 8509760379cfb417377a2e1ae4589ddca5f75f99..c425f2d4a9b0c9001628ccd733fec3a568faee6c 100644
--- a/src/sbbs3/exec.cpp
+++ b/src/sbbs3/exec.cpp
@@ -1658,6 +1658,12 @@ int sbbs_t::exec(csi_t *csi)
 							,useron.phone);
 						csi->logic=LOGIC_TRUE;
 						break;
+					case USER_STRING_IPADDR:
+						sprintf(useron.ipaddr,"%.*s",LEN_IPADDR,csi->str);
+						putuserrec(&cfg,useron.number,U_IPADDR,LEN_IPADDR
+							,useron.phone);
+						csi->logic=LOGIC_TRUE;
+						break;
 					case USER_STRING_COMMENT:
 						sprintf(useron.comment,"%.*s",LEN_COMMENT,csi->str);
 						putuserrec(&cfg,useron.number,U_COMMENT,LEN_COMMENT
diff --git a/src/sbbs3/execnet.cpp b/src/sbbs3/execnet.cpp
index 0c19b3b2b7d9d88d670e46cd5fe8d6564782391c..4e868e58549a73dce2021a63924dad0d6d767be3 100644
--- a/src/sbbs3/execnet.cpp
+++ b/src/sbbs3/execnet.cpp
@@ -42,6 +42,7 @@
 #define TIMEOUT_SOCK_LISTEN		30	/* seconds */
 #define TIMEOUT_FTP_RESPONSE	300	/* seconds */
 
+/* TODO: IPv6 */
 int sbbs_t::exec_net(csi_t* csi)
 {
 	char	str[512],rsp[512],buf[1025],ch,*p,**pp,**pp1,**pp2;
@@ -67,7 +68,7 @@ int sbbs_t::exec_net(csi_t* csi)
 					SOCKADDR_IN	addr;
 
 					memset(&addr,0,sizeof(addr));
-					addr.sin_addr.s_addr = htonl(startup->telnet_interface);
+					addr.sin_addr.s_addr = htonl(startup->outgoing4.s_addr);
 					addr.sin_family = AF_INET;
 
 					if((i=bind(sock, (struct sockaddr *) &addr, sizeof (addr)))!=0) {
@@ -550,10 +551,10 @@ SOCKET sbbs_t::ftp_data_sock(csi_t* csi, SOCKET ctrl_sock, SOCKADDR_IN* addr)
 	}
 
 	memset(addr,0,sizeof(SOCKADDR_IN));
-	addr->sin_addr.s_addr = htonl(startup->telnet_interface);
+	addr->sin_addr.s_addr = htonl(startup->outgoing4.s_addr);
 	addr->sin_family = AF_INET;
 
-	if(bind(data_sock, (struct sockaddr *)addr,sizeof(SOCKADDR_IN))!= 0) {
+	if(bind(data_sock, (struct sockaddr *)addr,xp_sockaddr_len(addr))!= 0) {
 		csi->socket_error=ERROR_VALUE;
 		close_socket(data_sock);
 		return(INVALID_SOCKET);
@@ -645,24 +646,25 @@ bool sbbs_t::ftp_get(csi_t* csi, SOCKET ctrl_sock, char* src, char* dest, bool d
 	BOOL		data_avail;
 	ulong		total=0;
 	SOCKET		data_sock;
-	SOCKADDR_IN	addr;
+	union xp_sockaddr	addr;
 	socklen_t	addr_len;
 	FILE*		fp=NULL;
 	struct timeval	tv;
 	fd_set			socket_set;
 
-	if((data_sock=ftp_data_sock(csi, ctrl_sock, &addr))==INVALID_SOCKET)
+	if((data_sock=ftp_data_sock(csi, ctrl_sock, &addr.in))==INVALID_SOCKET)
 		return(false);
 
 	if(csi->ftp_mode&CS_FTP_PASV) {
 
 #if 0	// Debug
 		bprintf("Connecting to %s:%hd\r\n"
-			,inet_ntoa(addr.sin_addr)
-			,ntohs(addr.sin_port));
+			,inet_ntoa(addr.in.sin_addr)
+			,ntohs(addr.in.sin_port));
 #endif
 
-		if(connect(data_sock,(struct sockaddr *)&addr,sizeof(addr))!=0) {
+		/* TODO: IPv6 */
+		if(connect(data_sock,&addr.addr,sizeof(SOCKADDR_IN))!=0) {
 			csi->socket_error=ERROR_VALUE;
 			close_socket(data_sock);
 			return(false);
@@ -699,7 +701,7 @@ bool sbbs_t::ftp_get(csi_t* csi, SOCKET ctrl_sock, char* src, char* dest, bool d
 		SOCKET accept_sock;
 
 		addr_len=sizeof(addr);
-		if((accept_sock=accept_socket(data_sock,(struct sockaddr*)&addr,&addr_len))
+		if((accept_sock=accept_socket(data_sock,&addr,&addr_len))
 			==INVALID_SOCKET) {
 			csi->socket_error=ERROR_VALUE;
 			closesocket(data_sock);
@@ -770,7 +772,7 @@ bool sbbs_t::ftp_put(csi_t* csi, SOCKET ctrl_sock, char* src, char* dest)
 	int			result;
 	ulong		total=0;
 	SOCKET		data_sock;
-	SOCKADDR_IN	addr;
+	union xp_sockaddr	addr;
 	socklen_t	addr_len;
 	FILE*		fp=NULL;
 	bool		error=false;
@@ -782,7 +784,7 @@ bool sbbs_t::ftp_put(csi_t* csi, SOCKET ctrl_sock, char* src, char* dest)
 	if(!fexistcase(path))
 		return(false);
 
-	if((data_sock=ftp_data_sock(csi, ctrl_sock, &addr))==INVALID_SOCKET) {
+	if((data_sock=ftp_data_sock(csi, ctrl_sock, &addr.in))==INVALID_SOCKET) {
 		bprintf("ftp: failure, line %d",__LINE__);
 		return(false);
 	}
@@ -791,11 +793,11 @@ bool sbbs_t::ftp_put(csi_t* csi, SOCKET ctrl_sock, char* src, char* dest)
 
 #if 0	// Debug
 		bprintf("Connecting to %s:%hd\r\n"
-			,inet_ntoa(addr.sin_addr)
-			,ntohs(addr.sin_port));
+			,inet_ntoa(addr.in.sin_addr)
+			,ntohs(addr.in.sin_port));
 #endif
 
-		if(connect(data_sock,(struct sockaddr *)&addr,sizeof(addr))!=0) {
+		if(connect(data_sock,&addr.addr,sizeof(addr.in))!=0) {
 			bprintf("ftp: failure, line %d",__LINE__);
 			csi->socket_error=ERROR_VALUE;
 			close_socket(data_sock);
@@ -837,7 +839,7 @@ bool sbbs_t::ftp_put(csi_t* csi, SOCKET ctrl_sock, char* src, char* dest)
 		SOCKET accept_sock;
 
 		addr_len=sizeof(addr);
-		if((accept_sock=accept_socket(data_sock,(struct sockaddr*)&addr,&addr_len))
+		if((accept_sock=accept_socket(data_sock,&addr,&addr_len))
 			==INVALID_SOCKET) {
 			csi->socket_error=ERROR_VALUE;
 			closesocket(data_sock);
diff --git a/src/sbbs3/ftpsrvr.c b/src/sbbs3/ftpsrvr.c
index 1e553969838a4fe0d738e6318ceb140a940b9a12..bee3410b0b683b117702dd98fe60dd8e53cc020e 100644
--- a/src/sbbs3/ftpsrvr.c
+++ b/src/sbbs3/ftpsrvr.c
@@ -53,6 +53,7 @@
 #include "telnet.h"
 #include "js_rtpool.h"
 #include "js_request.h"
+#include "multisock.h"
 
 /* Constants */
 
@@ -78,7 +79,7 @@
 
 static ftp_startup_t*	startup=NULL;
 static scfg_t	scfg;
-static SOCKET	server_socket=INVALID_SOCKET;
+static struct xpms_set *ftp_set = NULL;
 static protected_uint32_t active_clients;
 static protected_uint32_t thread_count;
 static volatile time_t	uptime=0;
@@ -106,7 +107,8 @@ char* genvpath(int lib, int dir, char* str);
 
 typedef struct {
 	SOCKET			socket;
-	SOCKADDR_IN		client_addr;
+	union xp_sockaddr	client_addr;
+	socklen_t		client_addr_len;
 } ftp_t;
 
 
@@ -220,18 +222,29 @@ static int32_t thread_down(void)
 	return count;
 }
 
-static SOCKET ftp_open_socket(int type)
+static void ftp_open_socket_cb(SOCKET sock, void *cbdata)
 {
-	SOCKET	sock;
 	char	error[256];
 
-	sock=socket(AF_INET, type, IPPROTO_IP);
-	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
+	if(startup!=NULL && startup->socket_open!=NULL)
 		startup->socket_open(startup->cbdata,TRUE);
-	if(sock!=INVALID_SOCKET) {
-		if(set_socket_options(&scfg, sock, "FTP", error, sizeof(error)))
-			lprintf(LOG_ERR,"%04d !ERROR %s",sock, error);
-	}
+	if(set_socket_options(&scfg, sock, "FTP", error, sizeof(error)))
+		lprintf(LOG_ERR,"%04d !ERROR %s",sock, error);
+}
+
+static void ftp_close_socket_cb(SOCKET sock, void *cbdata)
+{
+	if(startup!=NULL && startup->socket_open!=NULL)
+		startup->socket_open(startup->cbdata,FALSE);
+}
+
+static SOCKET ftp_open_socket(int domain, int type)
+{
+	SOCKET	sock;
+
+	sock=socket(domain, type, IPPROTO_IP);
+	if(sock != INVALID_SOCKET)
+		ftp_open_socket_cb(sock, NULL);
 	return(sock);
 }
 
@@ -1127,7 +1140,7 @@ int sockreadline(SOCKET socket, char* buf, int len, time_t* lastactive)
 
 		i=select(socket+1,&socket_set,NULL,NULL,&tv);
 
-		if(server_socket==INVALID_SOCKET || terminate_server) {
+		if(ftp_set==NULL || terminate_server) {
 			sockprintf(socket,"421 Server downed, aborting.");
 			lprintf(LOG_WARNING,"%04d Server downed, aborting",socket);
 			return(0);
@@ -1171,7 +1184,7 @@ int sockreadline(SOCKET socket, char* buf, int len, time_t* lastactive)
 
 void DLLCALL ftp_terminate(void)
 {
-   	lprintf(LOG_INFO,"%04d FTP Server terminate",server_socket);
+   	lprintf(LOG_INFO,"FTP Server terminate");
 	terminate_server=TRUE;
 }
 
@@ -1209,6 +1222,7 @@ static void send_thread(void* arg)
 	char		str[128];
 	char		tmp[128];
 	char		username[128];
+	char		host_ip[INET6_ADDRSTRLEN];
 	int			i;
 	int			rd;
 	int			wr;
@@ -1227,7 +1241,7 @@ static void send_thread(void* arg)
 	time_t		start;
 	time_t		last_report;
 	user_t		uploader;
-	SOCKADDR_IN	addr;
+	union xp_sockaddr	addr;
 	socklen_t	addr_len;
 	fd_set		socket_set;
 	struct timeval tv;
@@ -1287,7 +1301,7 @@ static void send_thread(void* arg)
 			error=TRUE;
 			break;
 		}
-		if(server_socket==INVALID_SOCKET || terminate_server) {
+		if(ftp_set==NULL || terminate_server) {
 			lprintf(LOG_WARNING,"%04d !DATA Transfer locally aborted",xfer.ctrl_sock);
 			sockprintf(xfer.ctrl_sock,"426 Transfer locally aborted.");
 			error=TRUE;
@@ -1422,8 +1436,9 @@ static void send_thread(void* arg)
 					if(!(scfg.dir[f.dir]->misc&DIR_QUIET)) {
 						addr_len = sizeof(addr);
 						if(uploader.level>=SYSOP_LEVEL
-							&& getpeername(xfer.ctrl_sock,(struct sockaddr *)&addr,&addr_len)==0)
-							SAFEPRINTF2(username,"%s [%s]",xfer.user->alias,inet_ntoa(addr.sin_addr));
+							&& getpeername(xfer.ctrl_sock,&addr.addr,&addr_len)==0
+							&& inet_addrtop(&addr, host_ip, sizeof(host_ip))!=NULL)
+							SAFEPRINTF2(username,"%s [%s]",xfer.user->alias,host_ip);
 						else
 							SAFECOPY(username,xfer.user->alias);
 						/* Inform uploader of downloaded file */
@@ -1447,7 +1462,7 @@ static void send_thread(void* arg)
 	}
 
 	fclose(fp);
-	if(server_socket!=INVALID_SOCKET && !terminate_server)
+	if(ftp_set!=NULL && !terminate_server)
 		*xfer.inprogress=FALSE;
 	if(xfer.tmpfile) {
 		if(!(startup->options&FTP_OPT_KEEP_TEMP_FILES))
@@ -1547,7 +1562,7 @@ static void receive_thread(void* arg)
 			error=TRUE;
 			break;
 		}
-		if(server_socket==INVALID_SOCKET || terminate_server) {
+		if(ftp_set==NULL || terminate_server) {
 			lprintf(LOG_WARNING,"%04d !DATA Transfer locally aborted",xfer.ctrl_sock);
 			/* Send NAK */
 			sockprintf(xfer.ctrl_sock,"426 Transfer locally aborted.");
@@ -1751,7 +1766,7 @@ static void receive_thread(void* arg)
 		sockprintf(xfer.ctrl_sock,"226 Upload complete (%lu cps).",cps);
 	}
 
-	if(server_socket!=INVALID_SOCKET && !terminate_server)
+	if(ftp_set!=NULL && !terminate_server)
 		*xfer.inprogress=FALSE;
 
 	thread_down();
@@ -1759,7 +1774,7 @@ static void receive_thread(void* arg)
 
 
 
-static void filexfer(SOCKADDR_IN* addr, SOCKET ctrl_sock, SOCKET pasv_sock, SOCKET* data_sock
+static void filexfer(union xp_sockaddr* addr, SOCKET ctrl_sock, SOCKET pasv_sock, SOCKET* data_sock
 					,char* filename, long filepos, BOOL* inprogress, BOOL* aborted
 					,BOOL delfile, BOOL tmpfile
 					,time_t* lastactive
@@ -1774,11 +1789,12 @@ static void filexfer(SOCKADDR_IN* addr, SOCKET ctrl_sock, SOCKET pasv_sock, SOCK
 	int			result;
 	ulong		l;
 	socklen_t	addr_len;
-	SOCKADDR_IN	server_addr;
+	union xp_sockaddr	server_addr;
 	BOOL		reuseaddr;
 	xfer_t*		xfer;
 	struct timeval	tv;
 	fd_set			socket_set;
+	char		host_ip[INET6_ADDRSTRLEN];
 
 	if((*inprogress)==TRUE) {
 		lprintf(LOG_WARNING,"%04d !TRANSFER already in progress",ctrl_sock);
@@ -1792,9 +1808,10 @@ static void filexfer(SOCKADDR_IN* addr, SOCKET ctrl_sock, SOCKET pasv_sock, SOCK
 	if(*data_sock!=INVALID_SOCKET)
 		ftp_close_socket(data_sock,__LINE__);
 
+	inet_addrtop(addr, host_ip, sizeof(host_ip));
 	if(pasv_sock==INVALID_SOCKET) {	/* !PASV */
 
-		if((*data_sock=socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) == INVALID_SOCKET) {
+		if((*data_sock=socket(addr->addr.sa_family, SOCK_STREAM, IPPROTO_IP)) == INVALID_SOCKET) {
 			lprintf(LOG_ERR,"%04d !DATA ERROR %d opening socket", ctrl_sock, ERROR_VALUE);
 			sockprintf(ctrl_sock,"425 Error %d opening socket",ERROR_VALUE);
 			if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
@@ -1811,16 +1828,19 @@ static void filexfer(SOCKADDR_IN* addr, SOCKET ctrl_sock, SOCKET pasv_sock, SOCK
 		reuseaddr=TRUE;
 		setsockopt(*data_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&reuseaddr,sizeof(reuseaddr));
 
-		memset(&server_addr, 0, sizeof(server_addr));
+		addr_len = sizeof(server_addr);
+		if((result=getsockname(ctrl_sock, &server_addr.addr,&addr_len))!=0) {
+			lprintf(LOG_ERR,"%04d !ERROR %d (%d) getting address/port of command socket (%u)"
+				,ctrl_sock,result,ERROR_VALUE,pasv_sock);
+			return;
+		}
 
-		server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
-		server_addr.sin_family = AF_INET;
-		server_addr.sin_port   = htons((WORD)(startup->port-1));	/* 20? */
+		inet_setaddrport(&server_addr, inet_addrport(&server_addr)-1);	/* 20? */
 
-		result=bind(*data_sock, (struct sockaddr *) &server_addr,sizeof(server_addr));
+		result=bind(*data_sock, &server_addr.addr,addr_len);
 		if(result!=0) {
-			server_addr.sin_port = 0;	/* any user port */
-			result=bind(*data_sock, (struct sockaddr *) &server_addr,sizeof(server_addr));
+			inet_setaddrport(&server_addr, 0);	/* any user port */
+			result=bind(*data_sock, &server_addr.addr,addr_len);
 		}
 		if(result!=0) {
 			lprintf(LOG_ERR,"%04d !DATA ERROR %d (%d) binding socket %d"
@@ -1833,11 +1853,11 @@ static void filexfer(SOCKADDR_IN* addr, SOCKET ctrl_sock, SOCKET pasv_sock, SOCK
 			return;
 		}
 
-		result=connect(*data_sock, (struct sockaddr *)addr,sizeof(struct sockaddr));
+		result=connect(*data_sock, &addr->addr,xp_sockaddr_len(addr));
 		if(result!=0) {
 			lprintf(LOG_WARNING,"%04d !DATA ERROR %d (%d) connecting to client %s port %u on socket %d"
 					,ctrl_sock,result,ERROR_VALUE
-					,inet_ntoa(addr->sin_addr),ntohs(addr->sin_port),*data_sock);
+					,host_ip,inet_addrport(addr),*data_sock);
 			sockprintf(ctrl_sock,"425 Error %d connecting to socket",ERROR_VALUE);
 			if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
 				ftp_remove(ctrl_sock, __LINE__, filename);
@@ -1847,18 +1867,18 @@ static void filexfer(SOCKADDR_IN* addr, SOCKET ctrl_sock, SOCKET pasv_sock, SOCK
 		}
 		if(startup->options&FTP_OPT_DEBUG_DATA)
 			lprintf(LOG_DEBUG,"%04d DATA socket %d connected to %s port %u"
-				,ctrl_sock,*data_sock,inet_ntoa(addr->sin_addr),ntohs(addr->sin_port));
+				,ctrl_sock,*data_sock,host_ip,inet_addrport(addr));
 
 	} else {	/* PASV */
 
 		if(startup->options&FTP_OPT_DEBUG_DATA) {
-			addr_len=sizeof(SOCKADDR_IN);
-			if((result=getsockname(pasv_sock, (struct sockaddr *)addr,&addr_len))!=0)
+			addr_len=sizeof(addr);
+			if((result=getsockname(pasv_sock, &addr->addr,&addr_len))!=0)
 				lprintf(LOG_ERR,"%04d !ERROR %d (%d) getting address/port of passive socket (%u)"
 					,ctrl_sock,result,ERROR_VALUE,pasv_sock);
 			else
 				lprintf(LOG_DEBUG,"%04d PASV DATA socket %d listening on %s port %u"
-					,ctrl_sock,pasv_sock,inet_ntoa(addr->sin_addr),ntohs(addr->sin_port));
+					,ctrl_sock,pasv_sock,host_ip,inet_addrport(addr));
 		}
 
 		/* Setup for select() */
@@ -1884,11 +1904,11 @@ static void filexfer(SOCKADDR_IN* addr, SOCKET ctrl_sock, SOCKET pasv_sock, SOCK
 			return;
 		}
 			
-		addr_len=sizeof(SOCKADDR_IN);
+		addr_len=sizeof(addr);
 #ifdef SOCKET_DEBUG_ACCEPT
 		socket_debug[ctrl_sock]|=SOCKET_DEBUG_ACCEPT;
 #endif
-		*data_sock=accept(pasv_sock,(struct sockaddr*)addr,&addr_len);
+		*data_sock=accept(pasv_sock,&addr->addr,&addr_len);
 #ifdef SOCKET_DEBUG_ACCEPT
 		socket_debug[ctrl_sock]&=~SOCKET_DEBUG_ACCEPT;
 #endif
@@ -1905,7 +1925,7 @@ static void filexfer(SOCKADDR_IN* addr, SOCKET ctrl_sock, SOCKET pasv_sock, SOCK
 			startup->socket_open(startup->cbdata,TRUE);
 		if(startup->options&FTP_OPT_DEBUG_DATA)
 			lprintf(LOG_DEBUG,"%04d PASV DATA socket %d connected to %s port %u"
-				,ctrl_sock,*data_sock,inet_ntoa(addr->sin_addr),ntohs(addr->sin_port));
+				,ctrl_sock,*data_sock,host_ip,inet_addrport(addr));
 	}
 
 	do {
@@ -2200,7 +2220,7 @@ void ftp_printfile(SOCKET sock, const char* name, unsigned code)
 	}
 }
 
-static BOOL ftp_hacklog(char* prot, char* user, char* text, char* host, SOCKADDR_IN* addr)
+static BOOL ftp_hacklog(char* prot, char* user, char* text, char* host, union xp_sockaddr* addr)
 {
 #ifdef _WIN32
 	if(startup->hack_sound[0] && !(startup->options&FTP_OPT_MUTE)) 
@@ -2214,17 +2234,20 @@ static BOOL ftp_hacklog(char* prot, char* user, char* text, char* host, SOCKADDR
 /* Consecutive failed login (possible password hack) attempt tracking		*/
 /****************************************************************************/
 
-static BOOL badlogin(SOCKET sock, ulong* login_attempts, char* user, char* passwd, char* host, SOCKADDR_IN* addr)
+static BOOL badlogin(SOCKET sock, ulong* login_attempts, char* user, char* passwd, char* host, union xp_sockaddr* addr)
 {
 	ulong count;
+	char	host_ip[INET6_ADDRSTRLEN];
 
 	if(addr!=NULL) {
 		count=loginFailure(startup->login_attempt_list, addr, "FTP", user, passwd);
 		if(startup->login_attempt_hack_threshold && count>=startup->login_attempt_hack_threshold)
 			ftp_hacklog("FTP LOGIN", user, passwd, host, addr);
-		if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold)
+		if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold) {
+			inet_addrtop(addr, host_ip, sizeof(host_ip));
 			filter_ip(&scfg, "FTP", "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
-				,host, inet_ntoa(addr->sin_addr), user, /* fname: */NULL);
+				,host, host_ip, user, /* fname: */NULL);
+		}
 		if(count > *login_attempts)
 			*login_attempts=count;
 	} else
@@ -2266,8 +2289,10 @@ static void ctrl_thread(void* arg)
 	char		aliasline[512];
 	char		desc[501]="";
 	char		sys_pass[128];
-	char*		host_name;
-	char		host_ip[64];
+	char		host_name[256];
+	char		host_ip[INET6_ADDRSTRLEN];
+	char		data_ip[INET6_ADDRSTRLEN];
+	uint16_t	data_port;
 	char		path[MAX_PATH+1];
 	char		local_dir[MAX_PATH+1];
 	char		ren_from[MAX_PATH+1]="";
@@ -2315,9 +2340,9 @@ static void ctrl_thread(void* arg)
 	SOCKET		pasv_sock=INVALID_SOCKET;
 	SOCKET		data_sock=INVALID_SOCKET;
 	HOSTENT*	host;
-	SOCKADDR_IN	addr;
-	SOCKADDR_IN	data_addr;
-	SOCKADDR_IN	pasv_addr;
+	union xp_sockaddr	addr;
+	union xp_sockaddr	data_addr;
+	union xp_sockaddr	pasv_addr;
 	ftp_t		ftp=*(ftp_t*)arg;
 	user_t		user;
 	time_t		t;
@@ -2347,10 +2372,9 @@ static void ctrl_thread(void* arg)
 	lastactive=time(NULL);
 
 	sock=ftp.socket;
-	data_addr=ftp.client_addr;
+	memcpy(&data_addr, &ftp.client_addr, ftp.client_addr_len);
 	/* Default data port is ctrl port-1 */
-	data_addr.sin_port=ntohs(data_addr.sin_port)-1;
-	data_addr.sin_port=htons(data_addr.sin_port);
+	data_port = inet_addrport(&data_addr)-1;
 	
 	lprintf(LOG_DEBUG,"%04d CTRL thread started", sock);
 
@@ -2378,21 +2402,17 @@ static void ctrl_thread(void* arg)
 
 	memset(&user,0,sizeof(user));
 
-	SAFECOPY(host_ip,inet_ntoa(ftp.client_addr.sin_addr));
+	inet_addrtop(&ftp.client_addr, host_ip, sizeof(host_ip));
 
 	lprintf(LOG_INFO,"%04d CTRL connection accepted from: %s port %u"
-		,sock, host_ip, ntohs(ftp.client_addr.sin_port));
+		,sock, host_ip, inet_addrport(&ftp.client_addr));
 
 	if(startup->options&FTP_OPT_NO_HOST_LOOKUP)
-		host=NULL;
-	else
-		host=gethostbyaddr ((char *)&ftp.client_addr.sin_addr
-			,sizeof(ftp.client_addr.sin_addr),AF_INET);
-
-	if(host!=NULL && host->h_name!=NULL)
-		host_name=host->h_name;
-	else
-		host_name="<no name>";
+		strcpy(host_name,"<no name>");
+	else {
+		if(getnameinfo(&ftp.client_addr.addr, sizeof(ftp.client_addr), host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD)!=0)
+			strcpy(host_name,"<no name>");
+	}
 
 	if(!(startup->options&FTP_OPT_NO_HOST_LOOKUP))
 		lprintf(LOG_INFO,"%04d Hostname: %s", sock, host_name);
@@ -2415,7 +2435,7 @@ static void ctrl_thread(void* arg)
 
 	/* For PASV mode */
 	addr_len=sizeof(pasv_addr);
-	if((result=getsockname(sock, (struct sockaddr *)&pasv_addr,&addr_len))!=0) {
+	if((result=getsockname(sock, &pasv_addr.addr,&addr_len))!=0) {
 		lprintf(LOG_ERR,"%04d !ERROR %d (%d) getting address/port", sock, result, ERROR_VALUE);
 		sockprintf(sock,"425 Error %d getting address/port",ERROR_VALUE);
 		ftp_close_socket(&sock,__LINE__);
@@ -2431,7 +2451,7 @@ static void ctrl_thread(void* arg)
 	client.time=time32(NULL);
 	SAFECOPY(client.addr,host_ip);
 	SAFECOPY(client.host,host_name);
-	client.port=ntohs(ftp.client_addr.sin_port);
+	client.port=inet_addrport(&ftp.client_addr);
 	client.protocol="FTP";
 	client.user="<unknown>";
 	client_on(sock,&client,FALSE /* update */);
@@ -2439,7 +2459,7 @@ static void ctrl_thread(void* arg)
 	if(startup->login_attempt_throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &ftp.client_addr)) > 1) {
 		lprintf(LOG_DEBUG,"%04d Throttling suspicious connection from: %s (%u login attempts)"
-			,sock, inet_ntoa(ftp.client_addr.sin_addr), login_attempts);
+			,sock, host_ip, login_attempts);
 		mswait(login_attempts*startup->login_attempt_throttle);
 	}
 
@@ -2690,7 +2710,7 @@ static void ctrl_thread(void* arg)
 			user.ltoday++;
 			SAFECOPY(user.modem,"FTP");
 			SAFECOPY(user.comp,host_name);
-			SAFECOPY(user.note,host_ip);
+			SAFECOPY(user.ipaddr,host_ip);
 			user.logontime=logintime;
 			putuserdat(&scfg, &user);
 
@@ -2788,7 +2808,7 @@ static void ctrl_thread(void* arg)
 		}
 #endif
 
-		if(strnicmp(cmd, "PORT ",5)==0 || strnicmp(cmd, "EPRT ",5)==0) {
+		if(strnicmp(cmd, "PORT ",5)==0 || strnicmp(cmd, "EPRT ",5)==0 || strnicmp(cmd, "LPRT ",5)==0) {
 
 			if(pasv_sock!=INVALID_SOCKET) 
 				ftp_close_socket(&pasv_sock,__LINE__);
@@ -2797,47 +2817,154 @@ static void ctrl_thread(void* arg)
 			SKIP_WHITESPACE(p);
 			if(strnicmp(cmd, "PORT ",5)==0) {
 				sscanf(p,"%u,%u,%u,%u,%hd,%hd",&h1,&h2,&h3,&h4,&p1,&p2);
-				data_addr.sin_addr.s_addr=htonl((h1<<24)|(h2<<16)|(h3<<8)|h4);
-				data_addr.sin_port=(u_short)((p1<<8)|p2);
-			} else { /* EPRT */
+				data_addr.in.sin_family=AF_INET;
+				data_addr.in.sin_addr.s_addr=htonl((h1<<24)|(h2<<16)|(h3<<8)|h4);
+				data_port = (p2<<8)|p1;
+			} else if(strnicmp(cmd, "EPRT ", 5)==0) { /* EPRT */
 				char	delim = *p;
 				int		prot;
+				char	addr_str[INET6_ADDRSTRLEN];
 
-				if(*p) p++;
+				memset(&data_addr, 0, sizeof(data_addr));
+				if(*p)
+					p++;
 				prot=strtol(p,NULL,/* base: */10);
-				if(prot!=1) {
-					lprintf(LOG_WARNING,"%04d UNSUPPORTED protocol: %d", sock, prot);
-					sockprintf(sock,"522 Network protocol not supported, use (1)");
+				switch(prot) {
+					case 1:
+						FIND_CHAR(p,delim);
+						if(*p)
+							p++;
+						data_addr.in.sin_addr.s_addr=inet_addr(p);
+						FIND_CHAR(p,delim);
+						if(*p)
+							p++;
+						data_port=atoi(p);
+						data_addr.in.sin_family=AF_INET;
+						break;
+					case 2:
+						FIND_CHAR(p,delim);
+						if(*p)
+							p++;
+						strncpy(addr_str, p, sizeof(addr_str));
+						addr_str[sizeof(addr_str)-1]=0;
+						tp=addr_str;
+						FIND_CHAR(tp, delim);
+						*tp=0;
+						if(inet_ptoaddr(addr_str, &data_addr, sizeof(data_addr))==NULL) {
+							lprintf(LOG_WARNING,"%04d Unable to parse IPv6 address %s",sock,addr_str);
+							sockprintf(sock,"522 Unable to parse IPv6 address (1)");
+							continue;
+						}
+						FIND_CHAR(p,delim);
+						if(*p)
+							p++;
+						data_port=atoi(p);
+						data_addr.in6.sin6_family=AF_INET6;
+						break;
+					default:
+						lprintf(LOG_WARNING,"%04d UNSUPPORTED protocol: %d", sock, prot);
+						sockprintf(sock,"522 Network protocol not supported, use (1)");
+						continue;
+				}
+			}
+			else {	/* LPRT */
+				if(sscanf(p,"%u,%u",&h1, &h2)!=2) {
+					lprintf(LOG_ERR, "Unable to parse LPRT %s", p);
+					sockprintf(sock, "521 Address family not supported");
 					continue;
 				}
-				FIND_CHAR(p,delim);
-				if(*p) p++;
-				data_addr.sin_addr.s_addr=inet_addr(p);
-				FIND_CHAR(p,delim);
-				if(*p) p++;
-				data_addr.sin_port=atoi(p);
+				FIND_CHAR(p,',');
+				if(*p)
+					p++;
+				FIND_CHAR(p,',');
+				if(*p)
+					p++;
+				switch(h1) {
+					case 4:	/* IPv4 */
+						if(h2 != 4) {
+							lprintf(LOG_ERR, "Unable to parse LPRT %s", p);
+							sockprintf(sock, "501 IPv4 Address is the wrong length");
+							continue;
+						}
+						for(h1 = 0; h1 < h2; h1++) {
+							((unsigned char *)(&data_addr.in.sin_addr))[h1]=atoi(p);
+							FIND_CHAR(p,',');
+							if(*p)
+								p++;
+						}
+						if(atoi(p)!=2) {
+							lprintf(LOG_ERR, "Unable to parse LPRT %s", p);
+							sockprintf(sock, "501 IPv4 Port is the wrong length");
+							continue;
+						}
+						FIND_CHAR(p,',');
+						if(*p)
+							p++;
+						for(h1 = 0; h1 < 2; h1++) {
+							((unsigned char *)(&data_port))[1-h1]=atoi(p);
+							FIND_CHAR(p,',');
+							if(*p)
+								p++;
+						}
+						data_addr.in.sin_family=AF_INET;
+						break;
+					case 6:	/* IPv6 */
+						if(h2 != 16) {
+							lprintf(LOG_ERR, "Unable to parse LPRT %s", p);
+							sockprintf(sock, "501 IPv6 Address is the wrong length");
+							continue;
+						}
+						for(h1 = 0; h1 < h2; h1++) {
+							((unsigned char *)(&data_addr.in6.sin6_addr))[h1]=atoi(p);
+							FIND_CHAR(p,',');
+							if(*p)
+								p++;
+						}
+						if(atoi(p)!=2) {
+							lprintf(LOG_ERR, "Unable to parse LPRT %s", p);
+							sockprintf(sock, "501 IPv6 Port is the wrong length");
+							continue;
+						}
+						FIND_CHAR(p,',');
+						if(*p)
+							p++;
+						for(h1 = 0; h1 < 2; h1++) {
+							((unsigned char *)(&data_port))[1-h1]=atoi(p);
+							FIND_CHAR(p,',');
+							if(*p)
+								p++;
+						}
+						data_addr.in6.sin6_family=AF_INET6;
+						break;
+					default:
+						lprintf(LOG_ERR, "Unable to parse LPRT %s", p);
+						sockprintf(sock, "521 Address family not supported");
+						continue;
+				}
 			}
-			if(data_addr.sin_port< IPPORT_RESERVED) {	
+
+			inet_addrtop(&data_addr, data_ip, sizeof(data_ip));
+			if(data_port< IPPORT_RESERVED) {
 				lprintf(LOG_WARNING,"%04d !SUSPECTED BOUNCE ATTACK ATTEMPT by %s to %s port %u"
 					,sock,user.alias
-					,inet_ntoa(data_addr.sin_addr),data_addr.sin_port);
+					,data_ip,data_port);
 				ftp_hacklog("FTP BOUNCE", user.alias, cmd, host_name, &ftp.client_addr);
 				sockprintf(sock,"504 Bad port number.");	
 				continue; /* As recommended by RFC2577 */
 			}
-			data_addr.sin_port=htons(data_addr.sin_port);
+			inet_setaddrport(&data_addr, data_port);
 			sockprintf(sock,"200 PORT Command successful.");
 			mode="active";
 			continue;
 		}
 
 		if(stricmp(cmd, "PASV")==0 || stricmp(cmd, "P@SW")==0	/* Kludge required for SMC Barricade V1.2 */
-			|| stricmp(cmd, "EPSV")==0) {	
+			|| stricmp(cmd, "EPSV")==0 || stricmp(cmd, "LPSV")==0) {	
 
 			if(pasv_sock!=INVALID_SOCKET) 
 				ftp_close_socket(&pasv_sock,__LINE__);
 
-			if((pasv_sock=ftp_open_socket(SOCK_STREAM))==INVALID_SOCKET) {
+			if((pasv_sock=ftp_open_socket(pasv_addr.addr.sa_family, SOCK_STREAM))==INVALID_SOCKET) {
 				lprintf(LOG_WARNING,"%04d !PASV ERROR %d opening socket", sock,ERROR_VALUE);
 				sockprintf(sock,"425 Error %d opening PASV data socket", ERROR_VALUE);
 				continue;
@@ -2860,9 +2987,9 @@ static void ctrl_thread(void* arg)
 					lprintf(LOG_DEBUG,"%04d PASV DATA trying to bind socket to port %u"
 						,sock,port);
 
-				pasv_addr.sin_port = htons(port);
+				inet_setaddrport(&pasv_addr, port);
 
-				if((result=bind(pasv_sock, (struct sockaddr *) &pasv_addr,sizeof(pasv_addr)))==0)
+				if((result=bind(pasv_sock, &pasv_addr.addr,xp_sockaddr_len(&pasv_addr)))==0)
 					break;
 				if(port==startup->pasv_port_high)
 					break;
@@ -2878,7 +3005,7 @@ static void ctrl_thread(void* arg)
 				lprintf(LOG_DEBUG,"%04d PASV DATA socket %d bound to port %u",sock,pasv_sock,port);
 
 			addr_len=sizeof(addr);
-			if((result=getsockname(pasv_sock, (struct sockaddr *)&addr,&addr_len))!=0) {
+			if((result=getsockname(pasv_sock, &addr.addr,&addr_len))!=0) {
 				lprintf(LOG_ERR,"%04d !PASV ERROR %d (%d) getting address/port"
 					,sock, result, ERROR_VALUE);
 				sockprintf(sock,"425 Error %d getting address/port",ERROR_VALUE);
@@ -2894,26 +3021,61 @@ static void ctrl_thread(void* arg)
 				continue;
 			}
 
-			/* Choose IP address to use in passive response */
-			ip_addr=0;
-			if(startup->options&FTP_OPT_LOOKUP_PASV_IP
-				&& (host=gethostbyname(startup->host_name))!=NULL) 
-				ip_addr=ntohl(*((ulong*)host->h_addr_list[0]));
-			if(ip_addr==0 && (ip_addr=startup->pasv_ip_addr)==0)
-				ip_addr=ntohl(pasv_addr.sin_addr.s_addr);
-
-			if(startup->options&FTP_OPT_DEBUG_DATA)
-				lprintf(LOG_INFO,"%04d PASV DATA IP address in response: %u.%u.%u.%u (subject to NAT)"
-					,sock
-					,(ip_addr>>24)&0xff
-					,(ip_addr>>16)&0xff
-					,(ip_addr>>8)&0xff
-					,ip_addr&0xff
-					);
-			port=ntohs(addr.sin_port);
+			port=inet_addrport(&addr);
 			if(stricmp(cmd, "EPSV")==0)
 				sockprintf(sock,"229 Entering Extended Passive Mode (|||%hu|)", port);
-			else
+			else if (stricmp(cmd,"LPSV")==0) {
+				switch(addr.addr.sa_family) {
+					case AF_INET:
+						sockprintf(sock, "228 Entering Long Passive Mode (4, 4, %d, %d, %d, %d, 2, %d, %d)"
+							,((unsigned char *)&(addr.in.sin_addr))[0]
+							,((unsigned char *)&(addr.in.sin_addr))[1]
+							,((unsigned char *)&(addr.in.sin_addr))[2]
+							,((unsigned char *)&(addr.in.sin_addr))[3]
+							,((unsigned char *)&(addr.in.sin_port))[0]
+							,((unsigned char *)&(addr.in.sin_port))[1]);
+						break;
+					case AF_INET6:
+						sockprintf(sock, "228 Entering Long Passive Mode (6, 16, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, 2, %d, %d)"
+							,((unsigned char *)&(addr.in6.sin6_addr))[0]
+							,((unsigned char *)&(addr.in6.sin6_addr))[1]
+							,((unsigned char *)&(addr.in6.sin6_addr))[2]
+							,((unsigned char *)&(addr.in6.sin6_addr))[3]
+							,((unsigned char *)&(addr.in6.sin6_addr))[4]
+							,((unsigned char *)&(addr.in6.sin6_addr))[5]
+							,((unsigned char *)&(addr.in6.sin6_addr))[6]
+							,((unsigned char *)&(addr.in6.sin6_addr))[7]
+							,((unsigned char *)&(addr.in6.sin6_addr))[8]
+							,((unsigned char *)&(addr.in6.sin6_addr))[9]
+							,((unsigned char *)&(addr.in6.sin6_addr))[10]
+							,((unsigned char *)&(addr.in6.sin6_addr))[11]
+							,((unsigned char *)&(addr.in6.sin6_addr))[12]
+							,((unsigned char *)&(addr.in6.sin6_addr))[13]
+							,((unsigned char *)&(addr.in6.sin6_addr))[14]
+							,((unsigned char *)&(addr.in6.sin6_addr))[15]
+							,((unsigned char *)&(addr.in6.sin6_port))[0]
+							,((unsigned char *)&(addr.in6.sin6_port))[1]);
+						break;
+				}
+			}
+			else {
+				/* Choose IP address to use in passive response */
+				ip_addr=0;
+				/* TODO: IPv6 this here lookup */
+				if(startup->options&FTP_OPT_LOOKUP_PASV_IP
+					&& (host=gethostbyname(startup->host_name))!=NULL) 
+					ip_addr=ntohl(*((ulong*)host->h_addr_list[0]));
+				if(ip_addr==0 && (ip_addr=startup->pasv_ip_addr.s_addr)==0)
+					ip_addr=ntohl(pasv_addr.in.sin_addr.s_addr);
+
+				if(startup->options&FTP_OPT_DEBUG_DATA)
+					lprintf(LOG_INFO,"%04d PASV DATA IP address in response: %u.%u.%u.%u (subject to NAT)"
+						,sock
+						,(ip_addr>>24)&0xff
+						,(ip_addr>>16)&0xff
+						,(ip_addr>>8)&0xff
+						,ip_addr&0xff
+						);
 				sockprintf(sock,"227 Entering Passive Mode (%u,%u,%u,%u,%hu,%hu)"
 					,(ip_addr>>24)&0xff
 					,(ip_addr>>16)&0xff
@@ -2922,6 +3084,7 @@ static void ctrl_thread(void* arg)
 					,(port>>8)&0xff
 					,port&0xff
 					);
+			}
 			mode="passive";
 			continue;
 		}
@@ -4422,7 +4585,7 @@ static void ctrl_thread(void* arg)
 		lprintf(LOG_DEBUG,"%04d Waiting for transfer to complete...",sock);
 		count=0;
 		while(transfer_inprogress==TRUE) {
-			if(server_socket==INVALID_SOCKET || terminate_server) {
+			if(ftp_set==NULL || terminate_server) {
 				mswait(2000);	/* allow xfer threads to terminate */
 				break;
 			}
@@ -4531,8 +4694,10 @@ static void cleanup(int code, int line)
 	semfile_list_free(&recycle_semfiles);
 	semfile_list_free(&shutdown_semfiles);
 
-	if(server_socket!=INVALID_SOCKET)
-		ftp_close_socket(&server_socket,__LINE__);
+	if(ftp_set != NULL) {
+		xpms_destroy(ftp_set, ftp_close_socket_cb, NULL);
+		ftp_set = NULL;
+	}
 
 	update_clients();	/* active_clients is destroyed below */
 
@@ -4584,18 +4749,16 @@ void DLLCALL ftp_server(void* arg)
 	char			error[256];
 	char			compiler[32];
 	char			str[256];
-	SOCKADDR_IN		server_addr;
-	SOCKADDR_IN		client_addr;
+	union xp_sockaddr client_addr;
 	socklen_t		client_addr_len;
 	SOCKET			client_socket;
 	int				i;
-	int				result;
 	time_t			t;
 	time_t			start;
 	time_t			initialized=0;
-	fd_set			socket_set;
 	ftp_t*			ftp;
-	struct timeval	tv;
+	struct in_addr	iaddr;
+	char			client_ip[INET6_ADDRSTRLEN];
 
 	ftp_ver();
 
@@ -4644,7 +4807,8 @@ void DLLCALL ftp_server(void* arg)
 	js_server_props.version_detail=ftp_ver();
 	js_server_props.clients=&active_clients.value;
 	js_server_props.options=&startup->options;
-	js_server_props.interface_addr=&startup->interface_addr;
+	/* TODO: IPv6 */
+	js_server_props.interface_addr=&startup->outgoing4;
 #endif
 
 	uptime=0;
@@ -4759,48 +4923,21 @@ void DLLCALL ftp_server(void* arg)
 			dotname(scfg.lib[i]->sname,scfg.lib[i]->sname);
 		}
 		/* open a socket and wait for a client */
-
-		if((server_socket=ftp_open_socket(SOCK_STREAM))==INVALID_SOCKET) {
-			lprintf(LOG_CRIT,"!ERROR %d opening socket", ERROR_VALUE);
-			cleanup(1,__LINE__);
-			break;
-		}
-
-		lprintf(LOG_DEBUG,"%04d FTP Server socket opened",server_socket);
-
-		/*****************************/
-		/* Listen for incoming calls */
-		/*****************************/
-		memset(&server_addr, 0, sizeof(server_addr));
-
-		server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
-		server_addr.sin_family = AF_INET;
-		server_addr.sin_port   = htons(startup->port);
-
-		if(startup->port < IPPORT_RESERVED) {
-			if(startup->seteuid!=NULL)
-				startup->seteuid(FALSE);
-		}
-		result=retry_bind(server_socket, (struct sockaddr *) &server_addr,sizeof(server_addr)
-			,startup->bind_retry_count,startup->bind_retry_delay,"FTP Server",lprintf);
-		if(startup->port < IPPORT_RESERVED) {
-			if(startup->seteuid!=NULL)
-				startup->seteuid(TRUE);
-		}
-		if(result!=0) {
-			lprintf(LOG_CRIT,"%04d %s", server_socket, BIND_FAILURE_HELP);
-			cleanup(1,__LINE__);
-			break;
+		ftp_set = xpms_create(startup->bind_retry_count, startup->bind_retry_delay, lprintf);
+		
+		if(ftp_set == NULL) {
+			lprintf(LOG_CRIT,"!ERROR %d creating FTP socket set", ERROR_VALUE);
+			cleanup(1, __LINE__);
+			return;
 		}
+		lprintf(LOG_DEBUG,"FTP Server socket set created");
 
-		if((result=listen(server_socket, 1))!= 0) {
-			lprintf(LOG_CRIT,"%04d !ERROR %d (%d) listening on socket"
-				,server_socket, result, ERROR_VALUE);
-			cleanup(1,__LINE__);
-			break;
-		}
+		/*
+		 * Add interfaces
+		 */
+		xpms_add_list(ftp_set, PF_UNSPEC, SOCK_STREAM, 0, startup->interfaces, startup->port, "FTP Server", ftp_open_socket_cb, startup->seteuid, NULL);
 
-		lprintf(LOG_INFO,"%04d FTP Server listening on port %u",server_socket,startup->port);
+		lprintf(LOG_INFO,"FTP Server listening");
 		status(STATUS_WFC);
 
 		/* Setup recycle/shutdown semaphore file lists */
@@ -4817,9 +4954,9 @@ void DLLCALL ftp_server(void* arg)
 		if(startup->started!=NULL)
     		startup->started(startup->cbdata);
 
-		lprintf(LOG_INFO,"%04d FTP Server thread started",server_socket);
+		lprintf(LOG_INFO,"FTP Server thread started");
 
-		while(server_socket!=INVALID_SOCKET && !terminate_server) {
+		while(ftp_set!=NULL && !terminate_server) {
 
 			if(protected_uint32_value(thread_count) <= 1) {
 				if(!(startup->options&FTP_OPT_NO_RECYCLE)) {
@@ -4842,53 +4979,22 @@ void DLLCALL ftp_server(void* arg)
 					break;
 				}
 			}
-			/* now wait for connection */
-
-			tv.tv_sec=startup->sem_chk_freq;
-			tv.tv_usec=0;
 
-			FD_ZERO(&socket_set);
-			FD_SET(server_socket,&socket_set);
-
-			if((i=select(server_socket+1,&socket_set,NULL,NULL,&tv))<1) {
-				if(i==0)
-					continue;
-				if(ERROR_VALUE==EINTR)
-					lprintf(LOG_DEBUG,"%04d FTP Server listening interrupted", server_socket);
-				else if(ERROR_VALUE == ENOTSOCK)
-            		lprintf(LOG_NOTICE,"%04d FTP Server socket closed", server_socket);
-				else
-					lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket",server_socket, ERROR_VALUE);
-				continue;
-			}
-
-			if(server_socket==INVALID_SOCKET || terminate_server)	/* terminated */
+			if(ftp_set==NULL || terminate_server)	/* terminated */
 				break;
 
+			/* now wait for connection */
 			client_addr_len = sizeof(client_addr);
-			client_socket = accept(server_socket, (struct sockaddr *)&client_addr
-        		,&client_addr_len);
+			client_socket = xpms_accept(ftp_set, &client_addr, &client_addr_len, startup->sem_chk_freq*1000, NULL);
 
 			if(client_socket == INVALID_SOCKET)
-			{
-#if 0	/* is this necessary still? */
-				if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINTR || ERROR_VALUE == EINVAL) {
-            		lprintf(LOG_NOTICE,"0000 FTP socket closed while listening");
-					break;
-				}
-#endif
-				lprintf(LOG_WARNING,"%04d !ERROR %d accepting connection"
-					,server_socket, ERROR_VALUE);
-#ifdef _WIN32
-				if(WSAGetLastError()==WSAENOBUFS)	/* recycle (re-init WinSock) on this error */
-					break;
-#endif
 				continue;
-			}
+
 			if(startup->socket_open!=NULL)
 				startup->socket_open(startup->cbdata,TRUE);
 
-			if(trashcan(&scfg,inet_ntoa(client_addr.sin_addr),"ip-silent")) {
+			inet_addrtop(&client_addr, client_ip, sizeof(client_ip));
+			if(trashcan(&scfg,client_ip,"ip-silent")) {
 				ftp_close_socket(&client_socket,__LINE__);
 				continue;
 			}
@@ -4912,7 +5018,8 @@ void DLLCALL ftp_server(void* arg)
 			}
 
 			ftp->socket=client_socket;
-			ftp->client_addr=client_addr;
+			memcpy(&ftp->client_addr, &client_addr, client_addr_len);
+			ftp->client_addr_len = client_addr_len;
 
 			protected_uint32_adjust(&thread_count,1);
 			_beginthread(ctrl_thread, 0, ftp);
@@ -4920,17 +5027,16 @@ void DLLCALL ftp_server(void* arg)
 		}
 
 #if 0 /* def _DEBUG */
-		lprintf(LOG_DEBUG,"0000 server_socket: %d",server_socket);
 		lprintf(LOG_DEBUG,"0000 terminate_server: %d",terminate_server);
 #endif
 		if(protected_uint32_value(active_clients)) {
-			lprintf(LOG_DEBUG,"%04d Waiting for %d active clients to disconnect..."
-				,server_socket, protected_uint32_value(active_clients));
+			lprintf(LOG_DEBUG,"Waiting for %d active clients to disconnect..."
+				, protected_uint32_value(active_clients));
 			start=time(NULL);
 			while(protected_uint32_value(active_clients)) {
 				if(time(NULL)-start>startup->max_inactivity) {
-					lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for %d active clients"
-						,server_socket, protected_uint32_value(active_clients));
+					lprintf(LOG_WARNING,"!TIMEOUT waiting for %d active clients"
+						, protected_uint32_value(active_clients));
 					break;
 				}
 				mswait(100);
diff --git a/src/sbbs3/ftpsrvr.h b/src/sbbs3/ftpsrvr.h
index 1bd27cbb0dbad0aa5e2b4a97c62f1de93f756852..aea75ef72267f79016e51d59b122fb2cf8fa20cd 100644
--- a/src/sbbs3/ftpsrvr.h
+++ b/src/sbbs3/ftpsrvr.h
@@ -51,8 +51,11 @@ typedef struct {
 	WORD	qwk_timeout;
 #define FTP_DEFAULT_QWK_TIMEOUT		600
 	WORD	sem_chk_freq;		/* semaphore file checking frequency (in seconds) */
-    DWORD   interface_addr;
-	DWORD	pasv_ip_addr;
+	struct in_addr outgoing4;
+	struct in6_addr	outgoing6;
+    str_list_t	interfaces;
+	struct in_addr pasv_ip_addr;
+	struct in6_addr	pasv_ip6_addr;
 	WORD	pasv_port_low;
 	WORD	pasv_port_high;
     DWORD	options;			/* See FTP_OPT definitions */
@@ -109,7 +112,7 @@ typedef struct {
 #if defined(STARTUP_INIT_FIELD_TABLES)
 static struct init_field ftp_init_fields[] = { 
 	 OFFSET_AND_SIZE(ftp_startup_t,port)
-	,OFFSET_AND_SIZE(ftp_startup_t,interface_addr)
+	,OFFSET_AND_SIZE(ftp_startup_t,interfaces)
 	,OFFSET_AND_SIZE(ftp_startup_t,ctrl_dir)
 	,OFFSET_AND_SIZE(ftp_startup_t,temp_dir)
 	,{ 0,0 }	/* terminator */
diff --git a/src/sbbs3/getctrl.c b/src/sbbs3/getctrl.c
index 7a17e3db626ab72b0a9a97023a0acc5a47b122af..3f6e1f809134122de24ddb19fda34c58c9f4df2b 100644
--- a/src/sbbs3/getctrl.c
+++ b/src/sbbs3/getctrl.c
@@ -20,7 +20,7 @@ char *get_ctrl_dir(char *path, size_t pathsz)
 	strncpy(path, PREFIX"/etc", pathsz);
 	if(pathsz > 0)
 		path[pathsz-1]=0;
-	iniFileName(ini_file, sizeof(ini_file)-1, PREFIX"/etc", sbbs.ini);
+	iniFileName(ini_file, sizeof(ini_file)-1, PREFIX"/etc", "sbbs.ini");
 	if(fexistcase(ini_file)) {
 		FILE*	fini;
 		char*	str;
diff --git a/src/sbbs3/ident.c b/src/sbbs3/ident.c
index 91c45cfc7a16b22cea31dc5281bea9b4adf8d6a3..d00703120a7c72b1015be90e4d92adf6878213f9 100644
--- a/src/sbbs3/ident.c
+++ b/src/sbbs3/ident.c
@@ -38,7 +38,7 @@
 #include "sbbs.h"
 #include "ident.h"
 
-BOOL identify(SOCKADDR_IN* client_addr, u_short local_port, char* buf
+BOOL identify(union xp_sockaddr *client_addr, u_short local_port, char* buf
 			   ,size_t maxlen, int timeout)
 {
 	char		req[128];
@@ -47,11 +47,13 @@ BOOL identify(SOCKADDR_IN* client_addr, u_short local_port, char* buf
 	int			rd;
 	ulong		val;
 	SOCKET		sock=INVALID_SOCKET;
-	SOCKADDR_IN	addr;
+	union xp_sockaddr	addr;
 	struct timeval	tv;
 	fd_set			socket_set;
 	BOOL		success=FALSE;
 
+	if(client_addr->addr.sa_family != AF_INET && client_addr->addr.sa_family != AF_INET6)
+		return FALSE;
 	if(timeout<=0)
 		timeout=IDENT_DEFAULT_TIMEOUT;
 
@@ -64,10 +66,10 @@ BOOL identify(SOCKADDR_IN* client_addr, u_short local_port, char* buf
 		val=1;
 		ioctlsocket(sock,FIONBIO,&val);	
 
-		addr=*client_addr;
-		addr.sin_port=htons(IPPORT_IDENT);
+		memcpy(&addr, client_addr, xp_sockaddr_len(client_addr));
+		inet_setaddrport(&addr, IPPORT_IDENT);
 
-		result=connect(sock, (struct sockaddr*)&addr, sizeof(addr));
+		result=connect(sock, &addr.addr, xp_sockaddr_len(&addr));
 
 		if(result==SOCKET_ERROR
 			&& (ERROR_VALUE==EWOULDBLOCK || ERROR_VALUE==EINPROGRESS)) {
@@ -99,7 +101,7 @@ BOOL identify(SOCKADDR_IN* client_addr, u_short local_port, char* buf
 			break;
 		}
 
-		sprintf(req,"%u,%u\r\n", ntohs(client_addr->sin_port), local_port);
+		sprintf(req,"%u,%u\r\n", inet_addrport(client_addr), local_port);
 		if(sendsocket(sock,req,strlen(req))!=(int)strlen(req)) {
 			sprintf(buf,"ERROR %d sending request",ERROR_VALUE);
 			break;
diff --git a/src/sbbs3/ident.h b/src/sbbs3/ident.h
index 64100aa41e25dd5fe35ca7c11c42dd81d9477e5b..b1767e9c628a5f358e6a4af8b458176d0546cc57 100644
--- a/src/sbbs3/ident.h
+++ b/src/sbbs3/ident.h
@@ -45,7 +45,7 @@
 extern "C" {   
 #endif   
 
-BOOL identify(SOCKADDR_IN* client_addr, u_short local_port, char* buf
+BOOL identify(union xp_sockaddr* client_addr, u_short local_port, char* buf
 			   ,size_t maxlen, int timeout /* in seconds */);
 
 #ifdef __cplusplus
diff --git a/src/sbbs3/js_global.c b/src/sbbs3/js_global.c
index 72ceb49caceeb82e07fe7fe30c4b1964af0bdabc..19bb062746e6019ae698d9ca54bb75f81141dfb5 100644
--- a/src/sbbs3/js_global.c
+++ b/src/sbbs3/js_global.c
@@ -3379,7 +3379,6 @@ js_socket_select(JSContext *cx, uintN argc, jsval *arglist)
 	struct		timeval tv = {0, 0};
 	jsuint		i;
     jsuint      limit;
-	SOCKET*		index;
 	jsval		val;
 	int			len=0;
 	jsrefcount	rc;
@@ -3405,9 +3404,6 @@ js_socket_select(JSContext *cx, uintN argc, jsval *arglist)
     if((rarray = JS_NewArrayObject(cx, 0, NULL))==NULL)
 		return(JS_FALSE);
 
-	if((index=(SOCKET *)malloc(sizeof(SOCKET)*limit))==NULL)
-		return(JS_FALSE);
-
 	FD_ZERO(&socket_set);
 	if(poll_for_write)
 		wr_set=&socket_set;
@@ -3417,10 +3413,8 @@ js_socket_select(JSContext *cx, uintN argc, jsval *arglist)
     for(i=0;i<limit;i++) {
         if(!JS_GetElement(cx, inarray, i, &val))
 			break;
-		sock=js_socket(cx,val);
-		index[i]=sock;
+		sock=js_socket_add(cx,val,&socket_set);
 		if(sock!=INVALID_SOCKET) {
-			FD_SET(sock,&socket_set);
 			if(sock>maxsock)
 				maxsock=sock;
 		}
@@ -3428,9 +3422,10 @@ js_socket_select(JSContext *cx, uintN argc, jsval *arglist)
 
 	rc=JS_SUSPENDREQUEST(cx);
 	if(select(maxsock+1,rd_set,wr_set,NULL,&tv) >= 0) {
-
 		for(i=0;i<limit;i++) {
-			if(index[i]!=INVALID_SOCKET && FD_ISSET(index[i],&socket_set)) {
+        	if(!JS_GetElement(cx, inarray, i, &val))
+				break;
+			if(js_socket_isset(cx,val,&socket_set)) {
 				val=INT_TO_JSVAL(i);
 				JS_RESUMEREQUEST(cx, rc);
    				if(!JS_SetElement(cx, rarray, len++, &val)) {
@@ -3443,7 +3438,6 @@ js_socket_select(JSContext *cx, uintN argc, jsval *arglist)
 
 		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(rarray));
 	}
-	free(index);
 	JS_RESUMEREQUEST(cx, rc);
 
     return(JS_TRUE);
@@ -3572,36 +3566,80 @@ js_strftime(JSContext *cx, uintN argc, jsval *arglist)
 	return(JS_TRUE);
 }
 
+/* TODO: IPv6 */
 static JSBool
 js_resolve_ip(JSContext *cx, uintN argc, jsval *arglist)
 {
 	jsval *argv=JS_ARGV(cx, arglist);
-	struct in_addr addr;
 	JSString*	str;
-	char*		p;
+	char*		p=NULL;
 	jsrefcount	rc;
+	struct addrinfo	hints,*res,*cur;
+	char		ip_str[INET6_ADDRSTRLEN];
+	BOOL		want_array=FALSE;
+	JSObject	*rarray;
+	unsigned	alen=0;
+	uintN		argn;
+	jsval		val;
+	int			result;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_NULL);
 
 	if(argc==0 || JSVAL_IS_VOID(argv[0]))
 		return(JS_TRUE);
 
-	JSVALUE_TO_MSTRING(cx, argv[0], p, NULL)
-	HANDLE_PENDING(cx);
-	if(p==NULL) 
+	for(argn=0; argn < argc; argn++) {
+		if(JSVAL_IS_BOOLEAN(argv[argn]))
+			want_array = JSVAL_TO_BOOLEAN(argv[argn]);
+		else if(JSVAL_IS_STRING(argv[argn])) {
+			if(p)
+				free(p);
+			JSVALUE_TO_MSTRING(cx, argv[argn], p, NULL)
+			HANDLE_PENDING(cx);
+		}
+	}
+	if(p==NULL)
 		return(JS_TRUE);
 
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_flags = AI_ADDRCONFIG;
+	hints.ai_socktype = SOCK_STREAM;
 	rc=JS_SUSPENDREQUEST(cx);
-	addr.s_addr=resolve_ip(p);
+	if((result=getaddrinfo(p, NULL, &hints, &res))!=0) {
+		lprintf(LOG_ERR, "!ERROR resolve_ip %s failed with error %d",p, result);
+		JS_RESUMEREQUEST(cx, rc);
+		free(p);
+		return JS_TRUE;
+	}
 	free(p);
-	JS_RESUMEREQUEST(cx, rc);
-	if(addr.s_addr==INADDR_NONE)
-		return(JS_TRUE);
-	
-	if((str=JS_NewStringCopyZ(cx, inet_ntoa(addr)))==NULL)
-		return(JS_FALSE);
 
-	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
+	if(want_array) {
+		JS_RESUMEREQUEST(cx, rc);
+		if((rarray = JS_NewArrayObject(cx, 0, NULL))==NULL)
+			return(JS_FALSE);
+		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(rarray));
+		for(cur=res; cur; cur=cur->ai_next) {
+			inet_addrtop((void *)cur->ai_addr, ip_str, sizeof(ip_str));
+			if((str=JS_NewStringCopyZ(cx, ip_str))==NULL) {
+				freeaddrinfo(res);
+				return(JS_FALSE);
+			}
+			val = STRING_TO_JSVAL(str);
+			if(!JS_SetElement(cx, rarray, alen++, &val))
+				break;
+		}
+		freeaddrinfo(res);
+	}
+	else {
+		inet_addrtop((void *)res->ai_addr, ip_str, sizeof(ip_str));
+		freeaddrinfo(res);
+		JS_RESUMEREQUEST(cx, rc);
+
+		if((str=JS_NewStringCopyZ(cx, ip_str))==NULL)
+			return(JS_FALSE);
+
+		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
+	}
 	return(JS_TRUE);
 }
 
@@ -3610,10 +3648,10 @@ static JSBool
 js_resolve_host(JSContext *cx, uintN argc, jsval *arglist)
 {
 	jsval *argv=JS_ARGV(cx, arglist);
-	struct in_addr addr;
-	HOSTENT*	h;
 	char*		p;
 	jsrefcount	rc;
+	struct addrinfo	hints,*res;
+	char		host_name[256];
 
 	JS_SET_RVAL(cx, arglist, JSVAL_NULL);
 
@@ -3622,17 +3660,29 @@ js_resolve_host(JSContext *cx, uintN argc, jsval *arglist)
 
 	JSVALUE_TO_MSTRING(cx, argv[0], p, NULL)
 	HANDLE_PENDING(cx);
-	if(p==NULL) 
+	if(p==NULL)
 		return(JS_TRUE);
 
 	rc=JS_SUSPENDREQUEST(cx);
-	addr.s_addr=inet_addr(p);
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_flags = AI_NUMERICHOST;
+	if(getaddrinfo(p, NULL, NULL, &res)!=0) {
+		free(p);
+		JS_RESUMEREQUEST(cx, rc);
+		return(JS_TRUE);
+	}
 	free(p);
-	h=gethostbyaddr((char *)&addr,sizeof(addr),AF_INET);
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_flags = NI_NAMEREQD;
+	if(getnameinfo(res->ai_addr, res->ai_addrlen, host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD)!=0) {
+		JS_RESUMEREQUEST(cx, rc);
+		return(JS_TRUE);
+	}
 	JS_RESUMEREQUEST(cx, rc);
 
-	if(h!=NULL && h->h_name!=NULL)
-		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx,h->h_name)));
+	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx,host_name)));
+	freeaddrinfo(res);
 
 	return(JS_TRUE);
 
@@ -4000,8 +4050,9 @@ static jsSyncMethodSpec js_global_functions[] = {
 	,311
 	},
 	{"gethostbyname",	js_resolve_ip,		1,	JSTYPE_ALIAS },
-	{"resolve_ip",		js_resolve_ip,		1,	JSTYPE_STRING,	JSDOCSTR("hostname")
-	,JSDOCSTR("resolve IP address of specified hostname (AKA gethostbyname)")
+	{"resolve_ip",		js_resolve_ip,		1,	JSTYPE_STRING,	JSDOCSTR("hostname [,array=<tt>false</tt>]")
+	,JSDOCSTR("resolve IP address of specified hostname (AKA gethostbyname).  If array is true (added in 3.17), will return "
+	"an array of all addresses rather than just the first one")
 	,311
 	},
 	{"gethostbyaddr",	js_resolve_host,	1,	JSTYPE_ALIAS },
diff --git a/src/sbbs3/js_socket.c b/src/sbbs3/js_socket.c
index 4047b65a31813a2196eef6f86eaa479353a540f6..730e0ba23e24e6d1757e12b7515d3292b8cc244d 100644
--- a/src/sbbs3/js_socket.c
+++ b/src/sbbs3/js_socket.c
@@ -38,27 +38,14 @@
 #include <cryptlib.h>
 
 #include "sbbs.h"
+#include "js_socket.h"
 #include "js_request.h"
+#include "multisock.h"
 
 #ifdef JAVASCRIPT
 
 int cryptInitialized=0;
 
-typedef struct
-{
-	SOCKET	sock;
-	BOOL	external;	/* externally created, don't close */
-	BOOL	debug;
-	BOOL	nonblocking;
-	BOOL	is_connected;
-	BOOL	network_byte_order;
-	int		last_error;
-	int		type;
-	SOCKADDR_IN	remote_addr;
-	CRYPT_SESSION	session;
-	char	*hostname;
-} private_t;
-
 static const char* getprivate_failure = "line %d %s JS_GetPrivate failed";
 
 static void do_cryptEnd(void)
@@ -68,13 +55,27 @@ static void do_cryptEnd(void)
 
 static int do_cryptAttribute(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_TYPE attr, int val)
 {
-	int ret=cryptSetAttribute(session, attr, val);
+	int ret;
+
+	/* Force "sane" values (requirements) */
+	switch(attr) {
+		case CRYPT_OPTION_NET_READTIMEOUT:
+			if (val < 0)
+				val = 0;
+			if (val > 300)
+				val = 300;
+			break;
+		default:
+			break;
+	}
+
+	ret=cryptSetAttribute(session, attr, val);
 	if(ret != CRYPT_OK)
-		lprintf(LOG_ERR, "cryptSetAttribute(%d) returned %d", attr, ret);
+		lprintf(LOG_ERR, "cryptSetAttribute(%d=%d) returned %d", attr, val, ret);
 	return ret;
 }
 
-int do_cryptInit(void)
+int DLLCALL do_cryptInit(void)
 {
 	int ret;
 
@@ -84,8 +85,12 @@ int do_cryptInit(void)
 			cryptInitialized=1;
 			atexit(do_cryptEnd);
 		}
-		else
-			lprintf(LOG_ERR,"cryptInit() returned %d", ret);
+		else {
+			if (ret == -12)	// This is a bit of a hack...
+				cryptInitialized=1;
+			else
+				lprintf(LOG_ERR,"cryptInit() returned %d", ret);
+		}
 	}
 	return cryptInitialized;
 }
@@ -94,31 +99,53 @@ static int do_cryptAttributeString(const CRYPT_CONTEXT session, CRYPT_ATTRIBUTE_
 {
 	int ret=cryptSetAttributeString(session, attr, val, len);
 	if(ret != CRYPT_OK)
-		lprintf(LOG_ERR, "cryptSetAttributeString(%d) returned %d", attr, ret);
+		lprintf(LOG_ERR, "cryptSetAttributeString(%d=%.*s) returned %d", attr, len, val, ret);
 	return ret;
 }
 
 static void do_CryptFlush(const CRYPT_CONTEXT session)
 {
 	int ret=cryptFlushData(session);
+	int		len = 0;
+	char	estr[CRYPT_MAX_TEXTSIZE+1];
+
+	ret = cryptFlushData(session);
 
-	if(ret!=CRYPT_OK)
-		lprintf(LOG_ERR, "cryptFlushData() returned %d", ret);
+	if(ret!=CRYPT_OK) {
+		cryptGetAttributeString(session, CRYPT_ATTRIBUTE_ERRORMESSAGE, estr, &len);
+		estr[len]=0;
+		if (len)
+			lprintf(LOG_ERR, "cryptFlushData() returned %d (%s)", ret, estr);
+		else
+			lprintf(LOG_ERR, "cryptFlushData() returned %d", ret);
+	}
+}
+
+static void do_js_close(js_socket_private_t *p)
+{
+	if(p->session != -1) {
+		cryptDestroySession(p->session);
+		p->session=-1;
+	}
+	if(p->sock==INVALID_SOCKET)
+		return;
+	close_socket(p->sock);
+	p->last_error = ERROR_VALUE;
+	p->sock = INVALID_SOCKET; 
+	p->is_connected = FALSE;
 }
 
-static ptrdiff_t js_socket_recv(private_t *p, void *buf, size_t len, int flags, int timeout)
+static ptrdiff_t js_socket_recv(js_socket_private_t *p, void *buf, size_t len, int flags, int timeout)
 {
 	ptrdiff_t	total=0;
 	int	copied,ret;
 	
 	if(p->session==-1)
 		return(recv(p->sock, buf, len, flags));	/* Blocked here, indefinitely, in MSP-UDP service */
-	if(p->nonblocking) {
-		do_cryptAttribute(p->session, CRYPT_OPTION_NET_READTIMEOUT, 0);
-	}
-	else {
-		do_cryptAttribute(p->session, CRYPT_OPTION_NET_READTIMEOUT, timeout);
-	}
+#if 0
+	if (do_cryptAttribute(p->session, CRYPT_OPTION_NET_READTIMEOUT, p->nonblocking?0:timeout) != CRYPT_OK)
+		return -1;
+#endif
 	do {
 		if((ret=cryptPopData(p->session, buf, len, &copied))==CRYPT_OK) {
 			if(p->nonblocking)
@@ -131,13 +158,16 @@ static ptrdiff_t js_socket_recv(private_t *p, void *buf, size_t len, int flags,
 		}
 		else {
 			lprintf(LOG_ERR,"cryptPopData() returned %d", ret);
-			return total;
+			if (total > 0)
+				return total;
+			do_js_close(p);
+			return -1;
 		}
 	} while(len);
 	return total;	// Shouldn't happen...
 }
 
-static ptrdiff_t js_socket_sendsocket(private_t *p, const void *msg, size_t len, int flush)
+static ptrdiff_t js_socket_sendsocket(js_socket_private_t *p, const void *msg, size_t len, int flush)
 {
 	ptrdiff_t total=0;
 	int copied=0,ret;
@@ -168,7 +198,7 @@ static ptrdiff_t js_socket_sendsocket(private_t *p, const void *msg, size_t len,
 	return total; // shouldn't happen...
 }
 
-static int js_socket_sendfilesocket(private_t *p, int file, off_t *offset, off_t count)
+static int js_socket_sendfilesocket(js_socket_private_t *p, int file, off_t *offset, off_t count)
 {
 	char		buf[1024*16];
 	off_t		len;
@@ -230,7 +260,7 @@ static int js_socket_sendfilesocket(private_t *p, int file, off_t *offset, off_t
 	return(total);
 }
 
-static void dbprintf(BOOL error, private_t* p, char* fmt, ...)
+static void dbprintf(BOOL error, js_socket_private_t* p, char* fmt, ...)
 {
 	va_list argptr;
 	char sbuf[1024];
@@ -250,9 +280,9 @@ static void dbprintf(BOOL error, private_t* p, char* fmt, ...)
 
 static void js_finalize_socket(JSContext *cx, JSObject *obj)
 {
-	private_t* p;
+	js_socket_private_t* p;
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL)
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL)
 		return;
 
 	if(p->session != -1) {
@@ -278,32 +308,19 @@ static JSBool
 js_close(JSContext *cx, uintN argc, jsval *arglist)
 {
 	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
-	private_t*	p;
+	js_socket_private_t*	p;
 	jsrefcount	rc;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
 
-	if(p->session != -1) {
-		cryptDestroySession(p->session);
-		p->session=-1;
-	}
-	if(p->sock==INVALID_SOCKET)
-		return(JS_TRUE);
-
 	rc=JS_SUSPENDREQUEST(cx);
-	close_socket(p->sock);
-
-	p->last_error = ERROR_VALUE;
-
+	do_js_close(p);
 	dbprintf(FALSE, p, "closed");
-
-	p->sock = INVALID_SOCKET; 
-	p->is_connected = FALSE;
 	JS_RESUMEREQUEST(cx, rc);
 
 	return(JS_TRUE);
@@ -354,6 +371,73 @@ SOCKET DLLCALL js_socket(JSContext *cx, jsval val)
 	return(sock);
 }
 
+SOCKET DLLCALL js_socket_add(JSContext *cx, jsval val, fd_set *fds)
+{
+	js_socket_private_t	*p;
+	JSClass*	cl;
+	SOCKET		sock=INVALID_SOCKET;
+	int32		i;
+
+	if(JSVAL_IS_OBJECT(val) && (cl=JS_GetClass(cx,JSVAL_TO_OBJECT(val)))!=NULL) {
+		if(cl->flags&JSCLASS_HAS_PRIVATE) {
+			if((p=(js_socket_private_t *)JS_GetPrivate(cx,JSVAL_TO_OBJECT(val)))!=NULL) {
+				if(p->set) {
+					for(i=0; i<p->set->sock_count; i++) {
+						if(p->set->socks[i].sock == INVALID_SOCKET)
+							continue;
+						FD_SET(p->set->socks[i].sock, fds);
+						if(p->set->socks[i].sock > sock)
+							sock = p->set->socks[i].sock;
+					}
+				}
+				else {
+					sock = p->sock;
+					if(sock != INVALID_SOCKET)
+						FD_SET(p->sock, fds);
+				}
+			}
+		}
+	} else if(val!=JSVAL_VOID) {
+		if(JS_ValueToInt32(cx,val,&i)) {
+			sock = i;
+			FD_SET(sock, fds);
+		}
+	}
+	return sock;
+}
+
+BOOL DLLCALL  js_socket_isset(JSContext *cx, jsval val, fd_set *fds)
+{
+	js_socket_private_t	*p;
+	JSClass*	cl;
+	int			i;
+
+	if(JSVAL_IS_OBJECT(val) && (cl=JS_GetClass(cx,JSVAL_TO_OBJECT(val)))!=NULL) {
+		if(cl->flags&JSCLASS_HAS_PRIVATE) {
+			if((p=(js_socket_private_t *)JS_GetPrivate(cx,JSVAL_TO_OBJECT(val)))!=NULL) {
+				if(p->set) {
+					for(i=0; i<p->set->sock_count; i++) {
+						if(p->set->socks[i].sock == INVALID_SOCKET)
+							continue;
+						if(FD_ISSET(p->set->socks[i].sock, fds))
+							return TRUE;
+					}
+				}
+				else {
+					if(FD_ISSET(p->sock, fds))
+						return TRUE;
+				}
+			}
+		}
+	} else if(val!=JSVAL_VOID) {
+		if(JS_ValueToInt32(cx,val,&i)) {
+			if(FD_ISSET(i, fds))
+				return TRUE;
+		}
+	}
+	return FALSE;
+}
+
 void DLLCALL js_timeval(JSContext* cx, jsval val, struct timeval* tv)
 {
 	jsdouble jsd;
@@ -372,40 +456,55 @@ js_bind(JSContext *cx, uintN argc, jsval *arglist)
 {
 	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
 	jsval *argv=JS_ARGV(cx, arglist);
-	ulong		ip=0;
-	private_t*	p;
+	js_socket_private_t*	p;
 	ushort		port=0;
-	SOCKADDR_IN	addr;
+	union xp_sockaddr	addr;
 	jsrefcount	rc;
-	char		*cstr;
+	char		*cstr=NULL;
+	char		portstr[6];
+	struct addrinfo	hints, *res, *tres;
+	int			ret;
 
-	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
+	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
 	
 	memset(&addr,0,sizeof(addr));
-	addr.sin_family = AF_INET;
+	memset(&hints, 0, sizeof(hints));
 
 	if(argc)
 		port = js_port(cx,argv[0],p->type);
-	addr.sin_port = htons(port);
-	if(argc > 1)
-		JSVALUE_TO_ASTRING(cx, argv[1], cstr, 16, NULL);
-	if(argc>1 && cstr != NULL
-		&& (ip=inet_addr(cstr))!=INADDR_NONE)
-		addr.sin_addr.s_addr = ip;
+	if(argc > 1) {
+		JSVALUE_TO_ASTRING(cx, argv[1], cstr, INET6_ADDRSTRLEN, NULL);
+	}
+
+	hints.ai_flags = AI_ADDRCONFIG|AI_NUMERICHOST|AI_NUMERICSERV|AI_PASSIVE;
+	hints.ai_socktype = p->type;
+
+	/* We need servname to be non-NULL so we can use a NULL hostname */
+	sprintf(portstr, "%hu", port);
 
 	rc=JS_SUSPENDREQUEST(cx);
-	if(bind(p->sock, (struct sockaddr *) &addr, sizeof(addr))!=0) {
-		p->last_error=ERROR_VALUE;
-		dbprintf(TRUE, p, "bind failed with error %d",ERROR_VALUE);
-		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
-		JS_RESUMEREQUEST(cx, rc);
+	if((ret=getaddrinfo(cstr, portstr, &hints, &res)) != 0) {
+		JS_RESUMEREQUEST(cx,rc);
+		dbprintf(TRUE, p, "getaddrinfo failed with error %d",ret);
 		return(JS_TRUE);
 	}
+	for(tres=res; tres->ai_next; tres=tres->ai_next) {
+		if(bind(p->sock, res->ai_addr, res->ai_addrlen)!=0) {
+			if (tres->ai_next == NULL) {
+				p->last_error=ERROR_VALUE;
+				dbprintf(TRUE, p, "bind failed with error %d",ERROR_VALUE);
+				freeaddrinfo(res);
+				JS_RESUMEREQUEST(cx, rc);
+				return(JS_TRUE);
+			}
+		}
+	}
+	freeaddrinfo(res);
 
 	dbprintf(FALSE, p, "bound to port %u",port);
 	JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
@@ -418,13 +517,13 @@ js_listen(JSContext *cx, uintN argc, jsval *arglist)
 {
 	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
 	jsval *argv=JS_ARGV(cx, arglist);
-	private_t*	p;
+	js_socket_private_t*	p;
 	int32		backlog=1;
 	jsrefcount	rc;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -451,8 +550,8 @@ static JSBool
 js_accept(JSContext *cx, uintN argc, jsval *arglist)
 {
 	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
-	private_t*	p;
-	private_t*	new_p;
+	js_socket_private_t*	p;
+	js_socket_private_t*	new_p;
 	JSObject*	sockobj;
 	SOCKET		new_socket;
 	socklen_t	addrlen;
@@ -460,7 +559,7 @@ js_accept(JSContext *cx, uintN argc, jsval *arglist)
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -468,11 +567,21 @@ js_accept(JSContext *cx, uintN argc, jsval *arglist)
 	addrlen=sizeof(p->remote_addr);
 
 	rc=JS_SUSPENDREQUEST(cx);
-	if((new_socket=accept_socket(p->sock,(struct sockaddr *)&(p->remote_addr),&addrlen))==INVALID_SOCKET) {
-		p->last_error=ERROR_VALUE;
-		dbprintf(TRUE, p, "accept failed with error %d",ERROR_VALUE);
-		JS_RESUMEREQUEST(cx, rc);
-		return(JS_TRUE);
+	if(p->set) {
+		if((new_socket=xpms_accept(p->set,&(p->remote_addr),&addrlen,XPMS_FOREVER,NULL))==INVALID_SOCKET) {
+			p->last_error=ERROR_VALUE;
+			dbprintf(TRUE, p, "accept failed with error %d",ERROR_VALUE);
+			JS_RESUMEREQUEST(cx, rc);
+			return(JS_TRUE);
+		}
+	}
+	else {
+		if((new_socket=accept_socket(p->sock,&(p->remote_addr),&addrlen))==INVALID_SOCKET) {
+			p->last_error=ERROR_VALUE;
+			dbprintf(TRUE, p, "accept failed with error %d",ERROR_VALUE);
+			JS_RESUMEREQUEST(cx, rc);
+			return(JS_TRUE);
+		}
 	}
 
 	if((sockobj=js_CreateSocketObject(cx, obj, "new_socket", new_socket))==NULL) {
@@ -481,7 +590,7 @@ js_accept(JSContext *cx, uintN argc, jsval *arglist)
 		JS_ReportError(cx,"Error creating new socket object");
 		return(JS_TRUE);
 	}
-	if((new_p=(private_t*)JS_GetPrivate(cx,sockobj))==NULL) {
+	if((new_p=(js_socket_private_t*)JS_GetPrivate(cx,sockobj))==NULL) {
 		JS_RESUMEREQUEST(cx, rc);
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
@@ -506,15 +615,16 @@ js_connect(JSContext *cx, uintN argc, jsval *arglist)
 	jsval *argv=JS_ARGV(cx, arglist);
 	int			result;
 	ulong		val;
-	ulong		ip_addr;
 	ushort		port;
 	JSString*	str;
-	private_t*	p;
+	js_socket_private_t*	p;
 	fd_set		socket_set;
 	struct		timeval tv = {0, 0};
 	jsrefcount	rc;
+	char		ip_str[256];
+	struct addrinfo	hints,*res,*cur;
 	
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -523,60 +633,62 @@ js_connect(JSContext *cx, uintN argc, jsval *arglist)
 	if(p->hostname)
 		free(p->hostname);
 	JSSTRING_TO_MSTRING(cx, str, p->hostname, NULL);
+	port = js_port(cx,argv[1],p->type);
 	rc=JS_SUSPENDREQUEST(cx);
 	dbprintf(FALSE, p, "resolving hostname: %s", p->hostname);
-	if((ip_addr=resolve_ip(p->hostname))==INADDR_NONE) {
-		p->last_error=ERROR_VALUE;
-		dbprintf(TRUE, p, "resolve_ip failed with error %d",ERROR_VALUE);
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_socktype = p->type;
+	hints.ai_flags = AI_ADDRCONFIG;
+	result = getaddrinfo(p->hostname, NULL, &hints, &res);
+	if(result != 0) {
 		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
+		dbprintf(TRUE, p, "looking up addresses for %s", p->hostname);
 		JS_RESUMEREQUEST(cx, rc);
 		return(JS_TRUE);
 	}
-
-	JS_RESUMEREQUEST(cx, rc);
-	port = js_port(cx,argv[1],p->type);
-
-	tv.tv_sec = 10;	/* default time-out */
-
-	if(argc>2)	/* time-out value specified */
-		js_timeval(cx,argv[2],&tv);
-
-	rc=JS_SUSPENDREQUEST(cx);
-	dbprintf(FALSE, p, "connecting to port %u at %s", port, p->hostname);
-
-	memset(&(p->remote_addr),0,sizeof(p->remote_addr));
-	p->remote_addr.sin_addr.s_addr = ip_addr;
-	p->remote_addr.sin_family = AF_INET;
-	p->remote_addr.sin_port   = htons(port);
-
 	/* always set to nonblocking here */
 	val=1;
 	ioctlsocket(p->sock,FIONBIO,&val);	
+	for(cur=res,result=1; result && cur; cur=cur->ai_next) {
+		tv.tv_sec = 10;	/* default time-out */
 
-	result=connect(p->sock, (struct sockaddr *)&(p->remote_addr), sizeof(p->remote_addr));
+		if(argc>2)	/* time-out value specified */
+			js_timeval(cx,argv[2],&tv);
 
-	if(result==SOCKET_ERROR
-		&& (ERROR_VALUE==EWOULDBLOCK || ERROR_VALUE==EINPROGRESS)) {
-		FD_ZERO(&socket_set);
-		FD_SET(p->sock,&socket_set);
-		if(select(p->sock+1,NULL,&socket_set,NULL,&tv)==1)
-			result=0;	/* success */
-	}
+		inet_addrtop((void *)cur->ai_addr, ip_str, sizeof(ip_str));
+		dbprintf(FALSE, p, "connecting to %s on port %u at %s", ip_str, port, p->hostname);
+		inet_setaddrport((void *)cur->ai_addr, port);
 
+		result=connect(p->sock, cur->ai_addr, cur->ai_addrlen);
+
+		if(result==SOCKET_ERROR
+				&& (ERROR_VALUE==EWOULDBLOCK || ERROR_VALUE==EINPROGRESS)) {
+			FD_ZERO(&socket_set);
+			FD_SET(p->sock,&socket_set);
+			if(select(p->sock+1,NULL,&socket_set,NULL,&tv)==1)
+				result=0;	/* success */
+		}
+		if(result==0)
+			break;
+	}
 	/* Restore original setting here */
 	ioctlsocket(p->sock,FIONBIO,(ulong*)&(p->nonblocking));
 
 	if(result!=0) {
+		freeaddrinfo(res);
 		p->last_error=ERROR_VALUE;
 		dbprintf(TRUE, p, "connect failed with error %d",ERROR_VALUE);
 		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
 		JS_RESUMEREQUEST(cx, rc);
 		return(JS_TRUE);
 	}
+	memcpy(&p->remote_addr, cur->ai_addr, cur->ai_addrlen);
+	freeaddrinfo(res);
 
 	p->is_connected = TRUE;
 	JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
-	dbprintf(FALSE, p, "connected to port %u at %s", port, p->hostname);
+	dbprintf(FALSE, p, "connected to %s on port %u at %s", ip_str, port, p->hostname);
 	JS_RESUMEREQUEST(cx, rc);
 
 	return(JS_TRUE);
@@ -590,12 +702,12 @@ js_send(JSContext *cx, uintN argc, jsval *arglist)
 	char*		cp;
 	size_t		len;
 	JSString*	str;
-	private_t*	p;
+	js_socket_private_t*	p;
 	jsrefcount	rc;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -622,6 +734,46 @@ js_send(JSContext *cx, uintN argc, jsval *arglist)
 	return(JS_TRUE);
 }
 
+static JSBool
+js_sendline(JSContext *cx, uintN argc, jsval *arglist)
+{
+	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
+	jsval *argv=JS_ARGV(cx, arglist);
+	char*		cp;
+	size_t		len;
+	JSString*	str;
+	js_socket_private_t*	p;
+	jsrefcount	rc;
+
+	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
+
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
+		JS_ReportError(cx,getprivate_failure,WHERE);
+		return(JS_FALSE);
+	}
+
+	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
+
+	str = JS_ValueToString(cx, argv[0]);
+	JSSTRING_TO_MSTRING(cx, str, cp, &len);
+	HANDLE_PENDING(cx);
+	if(cp==NULL)
+		return JS_TRUE;
+
+	rc=JS_SUSPENDREQUEST(cx);
+	if(js_socket_sendsocket(p,cp,len,FALSE)==len && js_socket_sendsocket(p,"\r\n",2,TRUE)==2) {
+		dbprintf(FALSE, p, "sent %u bytes",len+2);
+		JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
+	} else {
+		p->last_error=ERROR_VALUE;
+		dbprintf(TRUE, p, "send of %u bytes failed",len+2);
+	}
+	free(cp);
+	JS_RESUMEREQUEST(cx, rc);
+		
+	return(JS_TRUE);
+}
+
 static JSBool
 js_sendto(JSContext *cx, uintN argc, jsval *arglist)
 {
@@ -629,17 +781,18 @@ js_sendto(JSContext *cx, uintN argc, jsval *arglist)
 	jsval *argv=JS_ARGV(cx, arglist);
 	char*		cp;
 	size_t		len;
-	ulong		ip_addr;
 	ushort		port;
 	JSString*	data_str;
 	JSString*	ip_str;
-	private_t*	p;
-	SOCKADDR_IN	addr;
+	js_socket_private_t*	p;
 	jsrefcount	rc;
+	struct addrinfo hints,*res,*cur;
+	int			result;
+	char		ip_addr[INET6_ADDRSTRLEN];
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -666,38 +819,37 @@ js_sendto(JSContext *cx, uintN argc, jsval *arglist)
 		free(cp);
 		return JS_TRUE;
 	}
+	port = js_port(cx,argv[2],p->type);
 	rc=JS_SUSPENDREQUEST(cx);
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_socktype = p->type;
+	hints.ai_flags = AI_ADDRCONFIG;
 	dbprintf(FALSE, p, "resolving hostname: %s", p->hostname);
-	if((ip_addr=resolve_ip(p->hostname))==INADDR_NONE) {
-		p->last_error=ERROR_VALUE;
-		dbprintf(TRUE, p, "resolve_ip failed with error %d",ERROR_VALUE);
+
+	if((result=getaddrinfo(p->hostname, NULL, &hints, &res) != 0)) {
+		dbprintf(TRUE, p, "getaddrinfo failed with error %d",result);
 		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
 		free(cp);
 		JS_RESUMEREQUEST(cx, rc);
 		return(JS_TRUE);
 	}
 
-	/* port */
-	JS_RESUMEREQUEST(cx, rc);
-	port = js_port(cx,argv[2],p->type);
-	rc=JS_SUSPENDREQUEST(cx);
-
-	dbprintf(FALSE, p, "sending %d bytes to port %u at %s"
-		,len, port, p->hostname);
-
-	memset(&addr,0,sizeof(addr));
-	addr.sin_addr.s_addr = ip_addr;
-	addr.sin_family = AF_INET;
-	addr.sin_port   = htons(port);
-
-	if(sendto(p->sock,cp,len,0 /* flags */,(SOCKADDR*)&addr,sizeof(addr))==len) {
-		dbprintf(FALSE, p, "sent %u bytes",len);
-		JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
-	} else {
-		p->last_error=ERROR_VALUE;
-		dbprintf(TRUE, p, "send of %u bytes failed",len);
+	for(cur=res; cur; cur=cur->ai_next) {
+		inet_addrtop((void *)cur->ai_addr, ip_addr, sizeof(ip_addr));
+		dbprintf(FALSE, p, "sending %d bytes to %s port %u at %s"
+			,len, ip_addr, port, p->hostname);
+		inet_setaddrport((void *)cur->ai_addr, port);
+		if(sendto(p->sock,cp,len,0 /* flags */,cur->ai_addr,cur->ai_addrlen)==len) {
+			dbprintf(FALSE, p, "sent %u bytes",len);
+			JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
+		} else {
+			p->last_error=ERROR_VALUE;
+			dbprintf(TRUE, p, "send of %u bytes failed to %s",len, ip_addr);
+		}
 	}
 	free(cp);
+	freeaddrinfo(res);
 	JS_RESUMEREQUEST(cx, rc);
 
 	return(JS_TRUE);
@@ -712,12 +864,12 @@ js_sendfile(JSContext *cx, uintN argc, jsval *arglist)
 	char*		fname;
 	long		len;
 	int			file;
-	private_t*	p;
+	js_socket_private_t*	p;
 	jsrefcount	rc;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -764,12 +916,12 @@ js_sendbin(JSContext *cx, uintN argc, jsval *arglist)
 	int32		val=0;
 	size_t		wr=0;
 	int32		size=sizeof(DWORD);
-	private_t*	p;
+	js_socket_private_t*	p;
 	jsrefcount	rc;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -824,11 +976,11 @@ js_recv(JSContext *cx, uintN argc, jsval *arglist)
 	int32		len=512;
 	JSString*	str;
 	jsrefcount	rc;
-	private_t*	p;
+	js_socket_private_t*	p;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -873,7 +1025,7 @@ js_recvfrom(JSContext *cx, uintN argc, jsval *arglist)
 	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
 	jsval *argv=JS_ARGV(cx, arglist);
 	char*		buf;
-	char		ip_addr[64];
+	char		ip_addr[INET6_ADDRSTRLEN];
 	char		port[32];
 	int			rd=0;
 	int32		len=512;
@@ -885,14 +1037,14 @@ js_recvfrom(JSContext *cx, uintN argc, jsval *arglist)
 	jsval		data_val=JSVAL_NULL;
 	JSString*	str;
 	JSObject*	retobj;
-	SOCKADDR_IN	addr;
+	union xp_sockaddr	addr;
 	socklen_t	addrlen;
 	jsrefcount	rc;
-	private_t*	p;
+	js_socket_private_t*	p;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -915,11 +1067,11 @@ js_recvfrom(JSContext *cx, uintN argc, jsval *arglist)
 		rc=JS_SUSPENDREQUEST(cx);
 		switch(len) {
 			case sizeof(BYTE):
-				if((rd=recvfrom(p->sock,&b,len,0,(SOCKADDR*)&addr,&addrlen))==len)
+				if((rd=recvfrom(p->sock,&b,len,0,&addr.addr,&addrlen))==len)
 					data_val = INT_TO_JSVAL(b);
 				break;
 			case sizeof(WORD):
-				if((rd=recvfrom(p->sock,(BYTE*)&w,len,0,(SOCKADDR*)&addr,&addrlen))==len) {
+				if((rd=recvfrom(p->sock,(BYTE*)&w,len,0,&addr.addr,&addrlen))==len) {
 					if(p->network_byte_order)
 						w=ntohs(w);
 					data_val = INT_TO_JSVAL(w);
@@ -927,7 +1079,7 @@ js_recvfrom(JSContext *cx, uintN argc, jsval *arglist)
 				break;
 			default:
 			case sizeof(DWORD):
-				if((rd=recvfrom(p->sock,(BYTE*)&l,len,0,(SOCKADDR*)&addr,&addrlen))==len) {
+				if((rd=recvfrom(p->sock,(BYTE*)&l,len,0,&addr.addr,&addrlen))==len) {
 					if(p->network_byte_order)
 						l=ntohl(l);
 					data_val=UINT_TO_JSVAL(l);
@@ -950,7 +1102,7 @@ js_recvfrom(JSContext *cx, uintN argc, jsval *arglist)
 		}
 
 		rc=JS_SUSPENDREQUEST(cx);
-		len = recvfrom(p->sock,buf,len,0,(SOCKADDR*)&addr,&addrlen);
+		len = recvfrom(p->sock,buf,len,0,&addr.addr,&addrlen);
 		JS_RESUMEREQUEST(cx, rc);
 		if(len<0) {
 			p->last_error=ERROR_VALUE;
@@ -978,14 +1130,14 @@ js_recvfrom(JSContext *cx, uintN argc, jsval *arglist)
 		,data_val
 		,NULL,NULL,JSPROP_ENUMERATE);
 
-	sprintf(port,"%u",ntohs(addr.sin_port));
+	sprintf(port,"%u",inet_addrport(&addr));
 	if((str=JS_NewStringCopyZ(cx,port))==NULL)
 		return(JS_FALSE);
 	JS_DefineProperty(cx, retobj, "port"
 		,STRING_TO_JSVAL(str)
 		,NULL,NULL,JSPROP_ENUMERATE);
 
-	SAFECOPY(ip_addr,inet_ntoa(addr.sin_addr));
+	inet_addrtop(&addr, ip_addr, sizeof(ip_addr));
 	if((str=JS_NewStringCopyZ(cx,ip_addr))==NULL)
 		return(JS_FALSE);
 	JS_DefineProperty(cx, retobj, "ip_address"
@@ -1011,11 +1163,11 @@ js_peek(JSContext *cx, uintN argc, jsval *arglist)
 	int32		len=512;
 	JSString*	str;
 	jsrefcount	rc;
-	private_t*	p;
+	js_socket_private_t*	p;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -1056,7 +1208,7 @@ js_peek(JSContext *cx, uintN argc, jsval *arglist)
 }
 
 static int
-js_sock_read_check(private_t *p, time_t start, int32 timeout, int i)
+js_sock_read_check(js_socket_private_t *p, time_t start, int32 timeout, int i)
 {
 	BOOL		rd;
 
@@ -1090,17 +1242,17 @@ js_recvline(JSContext *cx, uintN argc, jsval *arglist)
 	jsval *argv=JS_ARGV(cx, arglist);
 	char		ch;
 	char*		buf;
-	int			i;
+	int			i,got;
 	int32		len=512;
 	time_t		start;
 	int32		timeout=30;	/* seconds */
 	JSString*	str;
-	private_t*	p;
+	js_socket_private_t*	p;
 	jsrefcount	rc;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -1135,12 +1287,16 @@ js_recvline(JSContext *cx, uintN argc, jsval *arglist)
 			}
 		}
 
-		if(js_socket_recv(p, &ch, 1, 0, timeout)!=1) {
+		if((got=js_socket_recv(p, &ch, 1, 0, i?1000:timeout))!=1) {
 			if(p->session==-1) {
 				p->last_error=ERROR_VALUE;
 				break;
 			}
 			else {
+				if (got == -1) {
+					len = 0;
+					continue;
+				}
 				switch(js_sock_read_check(p,start,timeout,i)) {
 					case 1:
 						JS_SET_RVAL(cx, arglist, JSVAL_NULL);
@@ -1191,12 +1347,12 @@ js_recvbin(JSContext *cx, uintN argc, jsval *arglist)
 	DWORD		l;
 	int32		size=sizeof(DWORD);
 	int			rd=0;
-	private_t*	p;
+	js_socket_private_t*	p;
 	jsrefcount	rc;
 
 	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -1242,7 +1398,7 @@ js_getsockopt(JSContext *cx, uintN argc, jsval *arglist)
 	int			opt;
 	int			level;
 	int			val;
-	private_t*	p;
+	js_socket_private_t*	p;
 	LINGER		linger;
 	void*		vp=&val;
 	socklen_t	len=sizeof(val);
@@ -1251,7 +1407,7 @@ js_getsockopt(JSContext *cx, uintN argc, jsval *arglist)
 
 	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -1294,7 +1450,7 @@ js_setsockopt(JSContext *cx, uintN argc, jsval *arglist)
 	int			opt;
 	int			level;
 	int32		val=1;
-	private_t*	p;
+	js_socket_private_t*	p;
 	LINGER		linger;
 	void*		vp=&val;
 	socklen_t	len=sizeof(val);
@@ -1303,7 +1459,7 @@ js_setsockopt(JSContext *cx, uintN argc, jsval *arglist)
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -1343,12 +1499,12 @@ js_ioctlsocket(JSContext *cx, uintN argc, jsval *arglist)
 	jsval *argv=JS_ARGV(cx, arglist);
 	int32		cmd=0;
 	int32		arg=0;
-	private_t*	p;
+	js_socket_private_t*	p;
 	jsrefcount	rc;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
@@ -1378,7 +1534,7 @@ js_poll(JSContext *cx, uintN argc, jsval *arglist)
 {
 	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
 	jsval *argv=JS_ARGV(cx, arglist);
-	private_t*	p;
+	js_socket_private_t*	p;
 	BOOL	poll_for_write=FALSE;
 	fd_set	socket_set;
 	fd_set*	rd_set=NULL;
@@ -1387,15 +1543,17 @@ js_poll(JSContext *cx, uintN argc, jsval *arglist)
 	int		result;
 	struct	timeval tv = {0, 0};
 	jsrefcount	rc;
+	int		i;
+	SOCKET	high=0;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		JS_ReportError(cx,getprivate_failure,WHERE);
 		return(JS_FALSE);
 	}
 
-	if(p->sock==INVALID_SOCKET) {
+	if(p->sock==INVALID_SOCKET && p->set == NULL) {
 		dbprintf(TRUE, p, "INVALID SOCKET in call to poll");
 		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
 		return(JS_TRUE);
@@ -1410,13 +1568,23 @@ js_poll(JSContext *cx, uintN argc, jsval *arglist)
 
 	rc=JS_SUSPENDREQUEST(cx);
 	FD_ZERO(&socket_set);
-	FD_SET(p->sock,&socket_set);
+	if(p->set) {
+		for(i=0; i<p->set->sock_count; i++) {
+			FD_SET(p->set->socks[i].sock,&socket_set);
+			if(p->set->socks[i].sock > high)
+				high = p->set->socks[i].sock;
+		}
+	}
+	else {
+		high=p->sock;
+		FD_SET(p->sock,&socket_set);
+	}
 	if(poll_for_write)
 		wr_set=&socket_set;
 	else
 		rd_set=&socket_set;
 
-	result = select(p->sock+1,rd_set,wr_set,NULL,&tv);
+	result = select(high+1,rd_set,wr_set,NULL,&tv);
 
 	p->last_error=ERROR_VALUE;
 
@@ -1479,12 +1647,12 @@ static JSBool js_socket_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict
 {
 	jsval idval;
     jsint       tiny;
-	private_t*	p;
+	js_socket_private_t*	p;
 	jsrefcount	rc;
 	BOOL		b;
 	int32		i;
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		// Prototype access
 		return(JS_TRUE);
 	}
@@ -1582,13 +1750,14 @@ static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 	ulong		cnt;
 	BOOL		rd;
 	BOOL		wr;
-	private_t*	p;
+	js_socket_private_t*	p;
 	JSString*	js_str;
-	SOCKADDR_IN	addr;
+	union xp_sockaddr	addr;
 	socklen_t	len=sizeof(addr);
 	jsrefcount	rc;
+	char		str[256];
 
-	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
+	if((p=(js_socket_private_t*)JS_GetPrivate(cx,obj))==NULL) {
 		// Protoype access
 		return(JS_TRUE);
 	}
@@ -1612,14 +1781,24 @@ static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 				*vp = BOOLEAN_TO_JSVAL(socket_check(p->sock,NULL,NULL,0));
 			break;
 		case SOCK_PROP_IS_WRITEABLE:
-			socket_check(p->sock,NULL,&wr,0);
+			if(p->sock==INVALID_SOCKET && p->set)
+				wr = FALSE;
+			else
+				socket_check(p->sock,NULL,&wr,0);
 			*vp = BOOLEAN_TO_JSVAL(wr);
 			break;
 		case SOCK_PROP_DATA_WAITING:
-			socket_check(p->sock,&rd,NULL,0);
+			if(p->sock==INVALID_SOCKET && p->set)
+				rd = FALSE;
+			else
+				socket_check(p->sock,&rd,NULL,0);
 			*vp = BOOLEAN_TO_JSVAL(rd);
 			break;
 		case SOCK_PROP_NREAD:
+			if(p->sock==INVALID_SOCKET && p->set) {
+				*vp = JSVAL_ZERO;
+				break;
+			}
 			cnt=0;
 			if(ioctlsocket(p->sock, FIONREAD, &cnt)==0) {
 				*vp=DOUBLE_TO_JSVAL((double)cnt);
@@ -1641,7 +1820,8 @@ static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 				if(getsockname(p->sock, (struct sockaddr *)&addr,&len)!=0)
 					return(JS_FALSE);
 				JS_RESUMEREQUEST(cx, rc);
-				if((js_str=JS_NewStringCopyZ(cx,inet_ntoa(addr.sin_addr)))==NULL)
+				inet_addrtop(&addr, str, sizeof(str));
+				if((js_str=JS_NewStringCopyZ(cx,str))==NULL)
 					return(JS_FALSE);
 				*vp = STRING_TO_JSVAL(js_str);
 				rc=JS_SUSPENDREQUEST(cx);
@@ -1651,13 +1831,10 @@ static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 			break;
 		case SOCK_PROP_LOCAL_PORT:
 			if(p->sock != INVALID_SOCKET) {
-				if(getsockname(p->sock, (struct sockaddr *)&addr,&len)!=0)
+				if(getsockname(p->sock, &addr.addr,&len)!=0)
 					return(JS_FALSE);
 				JS_RESUMEREQUEST(cx, rc);
-				if((js_str=JS_NewStringCopyZ(cx,inet_ntoa(addr.sin_addr)))==NULL)
-					return(JS_FALSE);
-
-				*vp = INT_TO_JSVAL(ntohs(addr.sin_port));
+				*vp = INT_TO_JSVAL(inet_addrport(&addr));
 				rc=JS_SUSPENDREQUEST(cx);
 			}
 			else
@@ -1666,7 +1843,8 @@ static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 		case SOCK_PROP_REMOTE_IP:
 			if(p->is_connected) {
 				JS_RESUMEREQUEST(cx, rc);
-				if((js_str=JS_NewStringCopyZ(cx,inet_ntoa(p->remote_addr.sin_addr)))==NULL)
+				inet_addrtop(&p->remote_addr, str, sizeof(str));
+				if((js_str=JS_NewStringCopyZ(cx,str))==NULL)
 					return(JS_FALSE);
 				*vp = STRING_TO_JSVAL(js_str);
 				rc=JS_SUSPENDREQUEST(cx);
@@ -1676,7 +1854,7 @@ static JSBool js_socket_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 			break;
 		case SOCK_PROP_REMOTE_PORT:
 			if(p->is_connected)
-				*vp = INT_TO_JSVAL(ntohs(p->remote_addr.sin_port));
+				*vp = INT_TO_JSVAL(inet_addrport(&p->remote_addr));
 			else
 				*vp=JSVAL_VOID;
 			break;
@@ -1748,6 +1926,11 @@ static jsSyncMethodSpec js_socket_functions[] = {
 	,JSDOCSTR("send a string (AKA write)")
 	,310
 	},
+	{"writeln",		js_sendline,		1,	JSTYPE_ALIAS },
+	{"sendline",	js_sendline,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("data")
+	,JSDOCSTR("send a string (AKA write) with a carriage return line feed appended")
+	,317
+	},
 	{"sendto",		js_sendto,		3,	JSTYPE_BOOLEAN,	JSDOCSTR("data, address, port")
 	,JSDOCSTR("send data to a specific host (IP address or host name) and port (number or service name), for UDP sockets")
 	,310
@@ -1884,7 +2067,7 @@ js_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
 	jsval *argv=JS_ARGV(cx, arglist);
 	int32	type=SOCK_STREAM;	/* default = TCP */
 	uintN	i;
-	private_t* p;
+	js_socket_private_t* p;
 	char*	protocol=NULL;
 
 	obj=JS_NewObject(cx, &js_socket_class, NULL, NULL);
@@ -1899,13 +2082,13 @@ js_socket_constructor(JSContext *cx, uintN argc, jsval *arglist)
 		}
 	}
 		
-	if((p=(private_t*)malloc(sizeof(private_t)))==NULL) {
+	if((p=(js_socket_private_t*)malloc(sizeof(js_socket_private_t)))==NULL) {
 		JS_ReportError(cx,"malloc failed");
 		if(protocol)
 			free(protocol);
 		return(JS_FALSE);
 	}
-	memset(p,0,sizeof(private_t));
+	memset(p,0,sizeof(js_socket_private_t));
 
 	if((p->sock=open_socket(type,protocol))==INVALID_SOCKET) {
 		JS_ReportError(cx,"open_socket failed with error %d",ERROR_VALUE);
@@ -1959,7 +2142,7 @@ JSObject* DLLCALL js_CreateSocketClass(JSContext* cx, JSObject* parent)
 JSObject* DLLCALL js_CreateSocketObject(JSContext* cx, JSObject* parent, char *name, SOCKET sock)
 {
 	JSObject*	obj;
-	private_t*	p;
+	js_socket_private_t*	p;
 	int			type=SOCK_STREAM;
 	socklen_t	len;
 
@@ -1975,9 +2158,9 @@ JSObject* DLLCALL js_CreateSocketObject(JSContext* cx, JSObject* parent, char *n
 	if(!js_DefineSocketOptionsArray(cx, obj, type))
 		return(NULL);
 
-	if((p=(private_t*)malloc(sizeof(private_t)))==NULL)
+	if((p=(js_socket_private_t*)malloc(sizeof(js_socket_private_t)))==NULL)
 		return(NULL);
-	memset(p,0,sizeof(private_t));
+	memset(p,0,sizeof(js_socket_private_t));
 
 	p->sock = sock;
 	p->external = TRUE;
@@ -1985,8 +2168,52 @@ JSObject* DLLCALL js_CreateSocketObject(JSContext* cx, JSObject* parent, char *n
 	p->session=-1;
 
 	len=sizeof(p->remote_addr);
-	if(getpeername(p->sock, (struct sockaddr *)&p->remote_addr,&len)==0)
+	if(getpeername(p->sock, &p->remote_addr.addr,&len)==0)
 		p->is_connected=TRUE;
+	else
+		lprintf(LOG_ERR, "Error %d calling getpeername()",errno);
+
+	if(!JS_SetPrivate(cx, obj, p)) {
+		dbprintf(TRUE, p, "JS_SetPrivate failed");
+		return(NULL);
+	}
+
+	dbprintf(FALSE, p, "object created");
+
+	return(obj);
+}
+
+JSObject* DLLCALL js_CreateSocketObjectFromSet(JSContext* cx, JSObject* parent, char *name, struct xpms_set *set)
+{
+	JSObject*	obj;
+	js_socket_private_t*	p;
+	int			type=SOCK_STREAM;
+	socklen_t	len;
+
+	obj = JS_DefineObject(cx, parent, name, &js_socket_class, NULL
+		,JSPROP_ENUMERATE|JSPROP_READONLY);
+
+	if(obj==NULL)
+		return(NULL);
+
+	if(set->sock_count < 1)
+		return NULL;
+
+	len = sizeof(type);
+	getsockopt(set->socks[0].sock,SOL_SOCKET,SO_TYPE,(void*)&type,&len);
+
+	if(!js_DefineSocketOptionsArray(cx, obj, type))
+		return(NULL);
+
+	if((p=(js_socket_private_t*)malloc(sizeof(js_socket_private_t)))==NULL)
+		return(NULL);
+	memset(p,0,sizeof(js_socket_private_t));
+
+	p->set = set;
+	p->sock = INVALID_SOCKET;
+	p->external = TRUE;
+	p->network_byte_order = TRUE;
+	p->session=-1;
 
 	if(!JS_SetPrivate(cx, obj, p)) {
 		dbprintf(TRUE, p, "JS_SetPrivate failed");
diff --git a/src/sbbs3/js_socket.h b/src/sbbs3/js_socket.h
index 67f01c3143785cea706d2e62f4951e41a35bff38..75f79519492bdd4fb0271e7ee67831cf6454fead 100644
--- a/src/sbbs3/js_socket.h
+++ b/src/sbbs3/js_socket.h
@@ -1,13 +1,34 @@
 #ifndef JS_SOCKET_H
 #define JS_SOCKET_H
 
+#include <cryptlib.h>
+#include "gen_defs.h"
+#include "sockwrap.h"
+#include "multisock.h"
+
+typedef struct
+{
+	SOCKET	sock;
+	BOOL	external;	/* externally created, don't close */
+	BOOL	debug;
+	BOOL	nonblocking;
+	BOOL	is_connected;
+	BOOL	network_byte_order;
+	int		last_error;
+	int		type;
+	union xp_sockaddr	remote_addr;
+	CRYPT_SESSION	session;
+	char	*hostname;
+	struct xpms_set	*set;
+} js_socket_private_t;
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 extern int cryptInitialized;
 
-int do_cryptInit(void);
+DLLEXPORT int DLLCALL do_cryptInit(void);
 
 #ifdef __cplusplus
 }
diff --git a/src/sbbs3/js_system.c b/src/sbbs3/js_system.c
index 109ea5945d6099e074b227f9a49ed03392d58980..1c1484b40dd8b40318bfa09a6bf204e8d0c3c4f1 100644
--- a/src/sbbs3/js_system.c
+++ b/src/sbbs3/js_system.c
@@ -1206,7 +1206,7 @@ js_hacklog(JSContext *cx, uintN argc, jsval *arglist)
 	char*		user=NULL;
 	char*		text=NULL;
 	char*		host=NULL;
-	SOCKADDR_IN	addr;
+	union xp_sockaddr	addr;
 	scfg_t*		cfg;
 	jsrefcount	rc;
 	BOOL		ret;
@@ -1220,10 +1220,10 @@ js_hacklog(JSContext *cx, uintN argc, jsval *arglist)
 	for(i=0;i<argc;i++) {
 		if(JSVAL_IS_NUMBER(argv[i])) {
 			JS_ValueToInt32(cx,argv[i],&i32);
-			if(addr.sin_addr.s_addr==0)
-				addr.sin_addr.s_addr=i32;
+			if(addr.in.sin_addr.s_addr==0)
+				addr.in.sin_addr.s_addr=i32;
 			else
-				addr.sin_port=(ushort)i32;
+				addr.in.sin_port=(ushort)i32;
 			continue;
 		}
 		if(!JSVAL_IS_STRING(argv[i]))
@@ -1550,7 +1550,7 @@ js_new_user(JSContext *cx, uintN argc, jsval *arglist)
 	if(client!=NULL) {
 		SAFECOPY(user.modem,client->protocol);
 		SAFECOPY(user.comp,client->host);
-		SAFECOPY(user.note,client->addr);
+		SAFECOPY(user.ipaddr,client->addr);
 	}
 
 	user.sex=' ';
diff --git a/src/sbbs3/js_user.c b/src/sbbs3/js_user.c
index 3636f4a18ed7800c35997705bd71e1d19da3ab50..33ed9a38371d5f4217b6c2f85c04d0727ce9141c 100644
--- a/src/sbbs3/js_user.c
+++ b/src/sbbs3/js_user.c
@@ -56,6 +56,7 @@ enum {
 	,USER_PROP_NAME		
 	,USER_PROP_HANDLE	
 	,USER_PROP_NOTE		
+	,USER_PROP_IPADDR
 	,USER_PROP_COMP		
 	,USER_PROP_COMMENT	
 	,USER_PROP_NETMAIL	
@@ -173,6 +174,9 @@ static JSBool js_user_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 		case USER_PROP_NOTE:
 			s=p->user->note;
 			break;
+		case USER_PROP_IPADDR:
+			s=p->user->ipaddr;
+			break;
 		case USER_PROP_COMP:
 			s=p->user->comp;
 			break;
@@ -465,6 +469,10 @@ static JSBool js_user_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict,
 			SAFECOPY(p->user->note,str);
 			putuserrec(scfg,p->user->number,U_NOTE,LEN_NOTE,str);
 			break;
+		case USER_PROP_IPADDR:		 
+			SAFECOPY(p->user->ipaddr,str);
+			putuserrec(scfg,p->user->number,U_IPADDR,LEN_IPADDR,str);
+			break;
 		case USER_PROP_COMP:
 			SAFECOPY(p->user->comp,str);
 			putuserrec(scfg,p->user->number,U_COMP,LEN_COMP,str);
@@ -733,7 +741,7 @@ static jsSyncPropertySpec js_user_properties[] = {
 	{	"alias"				,USER_PROP_ALIAS 		,USER_PROP_FLAGS,		310},
 	{	"name"				,USER_PROP_NAME		 	,USER_PROP_FLAGS,		310},
 	{	"handle"			,USER_PROP_HANDLE	 	,USER_PROP_FLAGS,		310},
-	{	"ip_address"		,USER_PROP_NOTE		 	,USER_PROP_FLAGS,		310},
+	{	"ip_address"		,USER_PROP_IPADDR	 	,USER_PROP_FLAGS,		310},
 	{	"note"				,USER_PROP_NOTE		 	,USER_PROP_FLAGS,		310},
 	{	"host_name"			,USER_PROP_COMP		 	,USER_PROP_FLAGS,		310},
 	{	"computer"			,USER_PROP_COMP		 	,USER_PROP_FLAGS,		310},
@@ -776,7 +784,7 @@ static char* user_prop_desc[] = {
 	,"real name"
 	,"chat handle"
 	,"IP address last logged on from"
-	,"AKA ip_address"
+	,"Sysop note (AKA ip_address on 3.16 and before)"
 	,"host name last logged on from"
 	,"AKA host_name"
 	,"sysop's comment"
diff --git a/src/sbbs3/logfile.cpp b/src/sbbs3/logfile.cpp
index 8845dab0be5e4686517b6d4744ba0d91dfb96e39..eee3810cecbbdfde14276666281097eb3f3e928b 100644
--- a/src/sbbs3/logfile.cpp
+++ b/src/sbbs3/logfile.cpp
@@ -37,12 +37,13 @@
 
 #include "sbbs.h"
 
-extern "C" BOOL DLLCALL hacklog(scfg_t* cfg, char* prot, char* user, char* text, char* host, SOCKADDR_IN* addr)
+extern "C" BOOL DLLCALL hacklog(scfg_t* cfg, char* prot, char* user, char* text, char* host, union xp_sockaddr* addr)
 {
 	char	hdr[1024];
 	char	tstr[64];
 	char	fname[MAX_PATH+1];
 	int		file;
+	char	ip[INET6_ADDRSTRLEN];
 	time32_t now=time32(NULL);
 
 	sprintf(fname,"%shack.log",cfg->logs_dir);
@@ -50,13 +51,14 @@ extern "C" BOOL DLLCALL hacklog(scfg_t* cfg, char* prot, char* user, char* text,
 	if((file=sopen(fname,O_CREAT|O_RDWR|O_BINARY|O_APPEND,SH_DENYWR,DEFFILEMODE))==-1)
 		return(FALSE);
 
+	inet_addrtop(addr, ip, sizeof(ip));
 	sprintf(hdr,"SUSPECTED %s HACK ATTEMPT for user '%s' on %.24s\r\nUsing port %u at %s [%s]\r\nDetails: "
 		,prot
 		,user
 		,timestr(cfg,now,tstr)
-		,addr->sin_port
+		,inet_addrport(addr)
 		,host
-		,inet_ntoa(addr->sin_addr)
+		,ip
 		);
 	write(file,hdr,strlen(hdr));
 	write(file,text,strlen(text));
diff --git a/src/sbbs3/login.cpp b/src/sbbs3/login.cpp
index 5f96eca1ee17c70a4c401c620cfadfa4363903da..62526b6d94fdeedba7e7dfd9164252519e703a18 100644
--- a/src/sbbs3/login.cpp
+++ b/src/sbbs3/login.cpp
@@ -152,7 +152,7 @@ void sbbs_t::badlogin(char* user, char* passwd)
 		::hacklog(&cfg, reason, user, passwd, client_name, &client_addr);
 	if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold)
 		filter_ip(&cfg, connection, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
-			,client_name, inet_ntoa(client_addr.sin_addr), user, /* fname: */NULL);
+			,client_name, client_ipaddr, user, /* fname: */NULL);
 
 	mswait(startup->login_attempt_delay);
 }
diff --git a/src/sbbs3/logon.cpp b/src/sbbs3/logon.cpp
index cabab50eb9611d20c649f0e20a5246fe0e15c287..d8f10f21c6c235bd72c61b5381ab082c948bcb11 100644
--- a/src/sbbs3/logon.cpp
+++ b/src/sbbs3/logon.cpp
@@ -418,11 +418,11 @@ bool sbbs_t::logon()
 			errormsg(WHERE,ERR_OPEN,str,O_RDWR|O_CREAT|O_APPEND);
 			return(false); 
 		}
-		getuserrec(&cfg,useron.number,U_NOTE,LEN_NOTE,useron.note);
+		getuserrec(&cfg,useron.number,U_IPADDR,LEN_IPADDR,useron.ipaddr);
 		getuserrec(&cfg,useron.number,U_LOCATION,LEN_LOCATION,useron.location);
 		sprintf(str,text[LastFewCallersFmt],cfg.node_num
 			,totallogons,useron.alias
-			,cfg.sys_misc&SM_LISTLOC ? useron.location : useron.note
+			,cfg.sys_misc&SM_LISTLOC ? useron.location : useron.ipaddr
 			,tm.tm_hour,tm.tm_min
 			,connection,useron.ltoday > 999 ? 999 : useron.ltoday);
 		write(file,str,strlen(str));
diff --git a/src/sbbs3/mailsrvr.c b/src/sbbs3/mailsrvr.c
index 1ecb66bfb20ff8145e457abd152ad0052f79e08c..d3639c77b6fd52245432309b5590321c1eefd3bf 100644
--- a/src/sbbs3/mailsrvr.c
+++ b/src/sbbs3/mailsrvr.c
@@ -58,6 +58,7 @@
 #include "xpendian.h"
 #include "js_rtpool.h"
 #include "js_request.h"
+#include "multisock.h"
 
 /* Constants */
 static const char*	server_name="Synchronet Mail Server";
@@ -86,9 +87,8 @@ static char* badrsp_err	=	"%s replied with:\r\n\"%s\"\r\n"
 
 static mail_startup_t* startup=NULL;
 static scfg_t	scfg;
-static SOCKET	server_socket=INVALID_SOCKET;
-static SOCKET	submission_socket=INVALID_SOCKET;
-static SOCKET	pop3_socket=INVALID_SOCKET;
+static struct xpms_set	*mail_set=NULL;
+static BOOL terminated=FALSE;
 static protected_uint32_t active_clients;
 static protected_uint32_t thread_count;
 static volatile int		active_sendmail=0;
@@ -137,7 +137,8 @@ struct mailproc {
 
 typedef struct {
 	SOCKET			socket;
-	SOCKADDR_IN		client_addr;
+	union xp_sockaddr	client_addr;
+	socklen_t		client_addr_len;
 } smtp_t,pop3_t;
 
 static int lprintf(int level, const char *fmt, ...)
@@ -229,26 +230,26 @@ static int32_t thread_down(void)
 	return count;
 }
 
-SOCKET mail_open_socket(int type, const char* protocol)
+void mail_open_socket(SOCKET sock, void* cb_protocol)
 {
+	char	*protocol=(char *)cb_protocol;
 	char	error[256];
 	char	section[128];
-	SOCKET	sock;
 
-	sock=socket(AF_INET, type, IPPROTO_IP);
-	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
+	if(startup!=NULL && startup->socket_open!=NULL)
 		startup->socket_open(startup->cbdata,TRUE);
-	if(sock!=INVALID_SOCKET) {
-		SAFEPRINTF(section,"mail|%s",protocol);
-		if(set_socket_options(&scfg, sock, section, error, sizeof(error)))
-			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
+	SAFEPRINTF(section,"mail|%s",protocol);
+	if(set_socket_options(&scfg, sock, section, error, sizeof(error)))
+		lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
 
-		stats.sockets++;
-#if 0 /*def _DEBUG */
-		lprintf(LOG_DEBUG,"%04d Socket opened (%d sockets in use)",sock,stats.sockets);
-#endif
-	}
-	return(sock);
+	stats.sockets++;
+}
+
+void mail_close_socket_cb(SOCKET sock, void* cb_protocol)
+{
+	if(startup!=NULL && startup->socket_open!=NULL)
+		startup->socket_open(startup->cbdata,FALSE);
+	stats.sockets--;
 }
 
 int mail_close_socket(SOCKET sock)
@@ -383,7 +384,7 @@ static int sockreadline(SOCKET socket, char* buf, int len)
 	
 	while(rd<len-1) {
 
-		if(server_socket==INVALID_SOCKET || terminate_server) {
+		if(terminated || terminate_server) {
 			lprintf(LOG_WARNING,"%04d !ABORTING sockreadline",socket);
 			return(-1);
 		}
@@ -724,9 +725,10 @@ static u_long resolve_ip(char *inaddr)
 /* A successful login from the same host resets the counter.				*/
 /****************************************************************************/
 
-static void badlogin(SOCKET sock, const char* prot, const char* resp, char* user, char* passwd, char* host, SOCKADDR_IN* addr)
+static void badlogin(SOCKET sock, const char* prot, const char* resp, char* user, char* passwd, char* host, union xp_sockaddr* addr)
 {
 	char	reason[128];
+	char	ip[INET6_ADDRSTRLEN];
 	ulong	count;
 
 	if(addr!=NULL) {
@@ -734,9 +736,10 @@ static void badlogin(SOCKET sock, const char* prot, const char* resp, char* user
 		count=loginFailure(startup->login_attempt_list, addr, prot, user, passwd);
 		if(startup->login_attempt_hack_threshold && count>=startup->login_attempt_hack_threshold)
 			hacklog(&scfg, reason, user, passwd, host, addr);
+		inet_addrtop(addr, ip, sizeof(ip));
 		if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold)
 			filter_ip(&scfg, (char*)prot, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
-				,host, inet_ntoa(addr->sin_addr), user, /* fname: */NULL);
+				,host, ip, user, /* fname: */NULL);
 	}
 
 	mswait(startup->login_attempt_delay);
@@ -749,7 +752,7 @@ static void pop3_thread(void* arg)
 	char		str[128];
 	char		buf[512];
 	char		host_name[128];
-	char		host_ip[64];
+	char		host_ip[INET6_ADDRSTRLEN];
 	char		username[128];
 	char		password[128];
 	char		challenge[256];
@@ -768,7 +771,6 @@ static void pop3_thread(void* arg)
 	long		msgnum;
 	ulong		bytes;
 	SOCKET		socket;
-	HOSTENT*	host;
 	smb_t		smb;
 	smbmsg_t	msg;
 	user_t		user;
@@ -791,22 +793,14 @@ static void pop3_thread(void* arg)
 		PlaySound(startup->pop3_sound, NULL, SND_ASYNC|SND_FILENAME);
 #endif
 
-	SAFECOPY(host_ip,inet_ntoa(pop3.client_addr.sin_addr));
+	inet_addrtop(&pop3.client_addr, host_ip, sizeof(host_ip));
 
 	if(startup->options&MAIL_OPT_DEBUG_POP3)
 		lprintf(LOG_INFO,"%04d POP3 connection accepted from: %s port %u"
-			,socket, host_ip, ntohs(pop3.client_addr.sin_port));
+			,socket, host_ip, inet_addrport(&pop3.client_addr));
 
-	if(startup->options&MAIL_OPT_NO_HOST_LOOKUP)
-		host=NULL;
-	else
-		host=gethostbyaddr((char *)&pop3.client_addr.sin_addr
-			,sizeof(pop3.client_addr.sin_addr),AF_INET);
-
-	if(host!=NULL && host->h_name!=NULL)
-		SAFECOPY(host_name,host->h_name);
-	else
-		strcpy(host_name,"<no name>");
+	if(getnameinfo(&pop3.client_addr.addr, pop3.client_addr_len, host_name, sizeof(host_name), NULL, 0, (startup->options&MAIL_OPT_NO_HOST_LOOKUP)?NI_NUMERICHOST:0)!=0)
+		SAFECOPY(host_name, "<no name>");
 
 	if(!(startup->options&MAIL_OPT_NO_HOST_LOOKUP) && (startup->options&MAIL_OPT_DEBUG_POP3))
 		lprintf(LOG_INFO,"%04d POP3 Hostname: %s", socket, host_name);
@@ -837,7 +831,7 @@ static void pop3_thread(void* arg)
 	client.time=time32(NULL);
 	SAFECOPY(client.addr,host_ip);
 	SAFECOPY(client.host,host_name);
-	client.port=ntohs(pop3.client_addr.sin_port);
+	client.port=inet_addrport(&pop3.client_addr);
 	client.protocol="POP3";
 	client.user="<unknown>";
 	client_on(socket,&client,FALSE /* update */);
@@ -848,7 +842,7 @@ static void pop3_thread(void* arg)
 	if(startup->login_attempt_throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &pop3.client_addr)) > 1) {
 		lprintf(LOG_DEBUG,"%04d POP3 Throttling suspicious connection from: %s (%u login attempts)"
-			,socket, inet_ntoa(pop3.client_addr.sin_addr), login_attempts);
+			,socket, host_ip, login_attempts);
 		mswait(login_attempts*startup->login_attempt_throttle);
 	}
 
@@ -956,7 +950,7 @@ static void pop3_thread(void* arg)
 			loginSuccess(startup->login_attempt_list, &pop3.client_addr);
 
 		putuserrec(&scfg,user.number,U_COMP,LEN_COMP,host_name);
-		putuserrec(&scfg,user.number,U_NOTE,LEN_NOTE,host_ip);
+		putuserrec(&scfg,user.number,U_IPADDR,LEN_IPADDR,host_ip);
 
 		/* Update client display */
 		client.user=user.alias;
@@ -1336,10 +1330,10 @@ static void pop3_thread(void* arg)
 	if(activity) {
 		if(user.number)
 			lprintf(LOG_INFO,"%04d POP3 %s logged out from port %u on %s [%s]"
-				,socket, user.alias, ntohs(pop3.client_addr.sin_port), host_name, host_ip);
+				,socket, user.alias, inet_addrport(&pop3.client_addr), host_name, host_ip);
 		else
 			lprintf(LOG_INFO,"%04d POP3 client disconnected from port %u on %s [%s]"
-				,socket, ntohs(pop3.client_addr.sin_port), host_name, host_ip);
+				,socket, inet_addrport(&pop3.client_addr), host_name, host_ip);
 	}
 
 	status(STATUS_WFC);
@@ -1366,22 +1360,69 @@ static void pop3_thread(void* arg)
 	mail_close_socket(socket);
 }
 
-static ulong rblchk(SOCKET sock, DWORD mail_addr_n, const char* rbl_addr)
+static ulong rblchk(SOCKET sock, union xp_sockaddr *addr, const char* rbl_addr)
 {
 	char		name[256];
 	DWORD		mail_addr;
 	HOSTENT*	host;
 	struct in_addr dnsbl_result;
-
-	mail_addr=ntohl(mail_addr_n);
-	safe_snprintf(name,sizeof(name),"%ld.%ld.%ld.%ld.%.128s"
-		,mail_addr&0xff
-		,(mail_addr>>8)&0xff
-		,(mail_addr>>16)&0xff
-		,(mail_addr>>24)&0xff
-		,rbl_addr
-		);
-
+	unsigned char	*addr6;
+
+	switch(addr->addr.sa_family) {
+		case AF_INET:
+			mail_addr=ntohl(addr->in.sin_addr.s_addr);
+			safe_snprintf(name,sizeof(name),"%ld.%ld.%ld.%ld.%.128s"
+				,mail_addr&0xff
+				,(mail_addr>>8)&0xff
+				,(mail_addr>>16)&0xff
+				,(mail_addr>>24)&0xff
+				,rbl_addr
+				);
+			break;
+		case AF_INET6:
+			addr6 = (unsigned char *)&addr->in6.sin6_addr;
+			safe_snprintf(name,sizeof(name),"%1x.%1x.%1x.%1x.%1x.%1x.%1x.%1x."
+											"%1x.%1x.%1x.%1x.%1x.%1x.%1x.%1x."
+											"%1x.%1x.%1x.%1x.%1x.%1x.%1x.%1x."
+											"%1x.%1x.%1x.%1x.%1x.%1x.%1x.%1x.%.128s"
+				,addr6[15]&0x0f
+				,addr6[15]>>4
+				,addr6[14]&0x0f
+				,addr6[14]>>4
+				,addr6[13]&0x0f
+				,addr6[13]>>4
+				,addr6[12]&0x0f
+				,addr6[12]>>4
+				,addr6[11]&0x0f
+				,addr6[11]>>4
+				,addr6[10]&0x0f
+				,addr6[10]>>4
+				,addr6[9]&0x0f
+				,addr6[9]>>4
+				,addr6[8]&0x0f
+				,addr6[8]>>4
+				,addr6[7]&0x0f
+				,addr6[7]>>4
+				,addr6[6]&0x0f
+				,addr6[6]>>4
+				,addr6[5]&0x0f
+				,addr6[5]>>4
+				,addr6[4]&0x0f
+				,addr6[4]>>4
+				,addr6[3]&0x0f
+				,addr6[3]>>4
+				,addr6[2]&0x0f
+				,addr6[2]>>4
+				,addr6[1]&0x0f
+				,addr6[1]>>4
+				,addr6[0]&0x0f
+				,addr6[0]>>4
+				,rbl_addr
+				);
+			break;
+		default:
+			return 0;
+	}
 	lprintf(LOG_DEBUG,"%04d SMTP DNSBL Query: %s",sock,name);
 
 	if((host=gethostbyname(name))==NULL)
@@ -1394,7 +1435,7 @@ static ulong rblchk(SOCKET sock, DWORD mail_addr_n, const char* rbl_addr)
 	return(dnsbl_result.s_addr);
 }
 
-static ulong dns_blacklisted(SOCKET sock, IN_ADDR addr, char* host_name, char* list, char* dnsbl_ip)
+static ulong dns_blacklisted(SOCKET sock, union xp_sockaddr *addr, char* host_name, char* list, char* dnsbl_ip)
 {
 	char	fname[MAX_PATH+1];
 	char	str[256];
@@ -1402,9 +1443,11 @@ static ulong dns_blacklisted(SOCKET sock, IN_ADDR addr, char* host_name, char* l
 	char*	tp;
 	FILE*	fp;
 	ulong	found=0;
+	char	ip[INET6_ADDRSTRLEN];
 
 	SAFEPRINTF(fname,"%sdnsbl_exempt.cfg",scfg.ctrl_dir);
-	if(findstr(inet_ntoa(addr),fname))
+	inet_addrtop(addr, ip, sizeof(ip));
+	if(findstr(ip,fname))
 		return(FALSE);
 	if(findstr(host_name,fname))
 		return(FALSE);
@@ -1430,11 +1473,11 @@ static ulong dns_blacklisted(SOCKET sock, IN_ADDR addr, char* host_name, char* l
 		FIND_WHITESPACE(tp);
 		*tp=0;	
 
-		found = rblchk(sock, addr.s_addr, p);
+		found = rblchk(sock, addr, p);
 	}
 	fclose(fp);
 	if(found)
-		strcpy(dnsbl_ip, inet_ntoa(addr));
+		strcpy(dnsbl_ip, ip);
 
 	return(found);
 }
@@ -2036,17 +2079,17 @@ static int parse_header_field(char* buf, smbmsg_t* msg, ushort* type)
 static int chk_received_hdr(SOCKET socket,const char *buf,IN_ADDR *dnsbl_result, char *dnsbl, char *dnsbl_ip)
 {
 	char		host_name[128];
-	IN_ADDR		check_addr;
 	char		*fromstr;
 	char		ip[16];
 	char		*p;
 	char		*p2;
 	char		*last;
+	union xp_sockaddr addr;
+	struct addrinfo ai,*res;
 
-	fromstr=(char *)malloc(strlen(buf)+1);
+	fromstr=strdup(buf);
 	if(fromstr==NULL)
 		return(0);
-	strcpy(fromstr,buf);
 	strlwr(fromstr);
 	do {
 		p=strstr(fromstr,"from ");
@@ -2067,11 +2110,28 @@ static int chk_received_hdr(SOCKET socket,const char *buf,IN_ADDR *dnsbl_result,
 		p=strtok_r(NULL,"]",&last);
 		if(p==NULL)
 			break;
-		strncpy(ip,p,16);
-		ip[15]=0;
-		check_addr.s_addr = inet_addr(ip);
-		lprintf(LOG_DEBUG,"%04d SMTP DNSBL checking received header address %s [%s]",socket,host_name,ip);
-		if((dnsbl_result->s_addr=dns_blacklisted(socket,check_addr,host_name,dnsbl,dnsbl_ip))!=0)
+		if(strnicmp("IPv6:", p, 5)) {
+			p+=5;
+			SKIP_WHITESPACE(p);
+			memset(&ai, 0, sizeof(ai));
+			ai.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV|AI_PASSIVE;
+			if(getaddrinfo(p, NULL, &ai, &res)!=0)
+				break;
+			if(res->ai_family == AF_INET6)
+				memcpy(&addr, res->ai_addr, res->ai_addrlen);
+			else
+				break;
+			freeaddrinfo(res);
+		}
+		else {
+			strncpy(ip,p,16);
+			ip[15]=0;
+			addr.in.sin_family=AF_INET;
+			addr.in.sin_addr.s_addr=inet_addr(ip);
+			lprintf(LOG_DEBUG,"%04d SMTP DNSBL checking received header address %s [%s]",socket,host_name,ip);
+		}
+
+		if((dnsbl_result->s_addr=dns_blacklisted(socket,&addr,host_name,dnsbl,dnsbl_ip))!=0)
 				lprintf(LOG_NOTICE,"%04d SMTP BLACKLISTED SERVER on %s: %s [%s] = %s"
 					,socket, dnsbl, host_name, ip, inet_ntoa(*dnsbl_result));
 	} while(0);
@@ -2241,9 +2301,10 @@ static void smtp_thread(void* arg)
 	char		spam_block[MAX_PATH+1];
 	char		spam_block_exempt[MAX_PATH+1];
 	char		host_name[128];
-	char		host_ip[64];
+	char		host_ip[INET6_ADDRSTRLEN];
+	char		server_ip[INET6_ADDRSTRLEN];
 	char		dnsbl[256];
-	char		dnsbl_ip[64];
+	char		dnsbl_ip[INET6_ADDRSTRLEN];
 	char*		telegram_buf;
 	char*		msgbuf;
 	char		challenge[256];
@@ -2287,7 +2348,6 @@ static void smtp_thread(void* arg)
 	char		session_id[MAX_PATH+1];
 	FILE*		spy=NULL;
 	SOCKET		socket;
-	HOSTENT*	host;
 	int			smb_error;
 	smb_t		smb;
 	smb_t		spam;
@@ -2298,7 +2358,7 @@ static void smtp_thread(void* arg)
 	node_t		node;
 	client_t	client;
 	smtp_t		smtp=*(smtp_t*)arg;
-	SOCKADDR_IN server_addr;
+	union xp_sockaddr	server_addr;
 	IN_ADDR		dnsbl_result;
 	BOOL*		mailproc_to_match;
 	int			mailproc_match;
@@ -2348,14 +2408,14 @@ static void smtp_thread(void* arg)
 #endif
 
 	addr_len=sizeof(server_addr);
-	if((i=getsockname(socket, (struct sockaddr *)&server_addr,&addr_len))!=0) {
+	if((i=getsockname(socket, &server_addr.addr, &addr_len))!=0) {
 		lprintf(LOG_CRIT,"%04d !SMTP ERROR %d (%d) getting address/port"
 			,socket, i, ERROR_VALUE);
 		sockprintf(socket,sys_error);
 		mail_close_socket(socket);
 		thread_down();
 		return;
-	} 
+	}
 
 	if((mailproc_to_match=malloc(sizeof(BOOL)*mailproc_count))==NULL) {
 		lprintf(LOG_CRIT,"%04d !SMTP ERROR allocating memory for mailproc_to_match", socket);
@@ -2372,21 +2432,13 @@ static void smtp_thread(void* arg)
 	memset(&user,0,sizeof(user));
 	memset(&relay_user,0,sizeof(relay_user));
 
-	SAFECOPY(host_ip,inet_ntoa(smtp.client_addr.sin_addr));
+	inet_addrtop(&smtp.client_addr,host_ip,sizeof(host_ip));
 
 	lprintf(LOG_INFO,"%04d SMTP Connection accepted on port %u from: %s port %u"
-		,socket, BE_INT16(server_addr.sin_port), host_ip, ntohs(smtp.client_addr.sin_port));
+		,socket, inet_addrport(&server_addr), host_ip, inet_addrport(&smtp.client_addr));
 
-	if(startup->options&MAIL_OPT_NO_HOST_LOOKUP)
-		host=NULL;
-	else
-		host=gethostbyaddr ((char *)&smtp.client_addr.sin_addr
-			,sizeof(smtp.client_addr.sin_addr),AF_INET);
-
-	if(host!=NULL && host->h_name!=NULL)
-		SAFECOPY(host_name,host->h_name);
-	else
-		strcpy(host_name,"<no name>");
+	if(getnameinfo(&smtp.client_addr.addr, smtp.client_addr_len, host_name, sizeof(host_name), NULL, 0, (startup->options&MAIL_OPT_NO_HOST_LOOKUP)?NI_NUMERICHOST:0)!=0)
+		SAFECOPY(host_name, "<no name>");
 
 	if(!(startup->options&MAIL_OPT_NO_HOST_LOOKUP))
 		lprintf(LOG_INFO,"%04d SMTP Hostname: %s", socket, host_name);
@@ -2400,8 +2452,9 @@ static void smtp_thread(void* arg)
 	SAFEPRINTF(spam_block,"%sspamblock.cfg",scfg.ctrl_dir);
 	SAFEPRINTF(spam_block_exempt,"%sspamblock_exempt.cfg",scfg.ctrl_dir);
 
-	if(smtp.client_addr.sin_addr.s_addr==server_addr.sin_addr.s_addr 
-		|| smtp.client_addr.sin_addr.s_addr==htonl(IPv4_LOCALHOST)) {
+	inet_addrtop(&server_addr,server_ip,sizeof(server_ip));
+
+	if(strcmp(server_ip, host_ip)==0) {
 		/* local connection */
 		dnsbl_result.s_addr=0;
 	} else {
@@ -2432,7 +2485,7 @@ static void smtp_thread(void* arg)
 		}
 
 		/*  SPAM Filters (mail-abuse.org) */
-		dnsbl_result.s_addr = dns_blacklisted(socket,smtp.client_addr.sin_addr,host_name,dnsbl,dnsbl_ip);
+		dnsbl_result.s_addr = dns_blacklisted(socket,&smtp.client_addr,host_name,dnsbl,dnsbl_ip);
 		if(dnsbl_result.s_addr) {
 			lprintf(LOG_NOTICE,"%04d SMTP BLACKLISTED SERVER on %s: %s [%s] = %s"
 				,socket, dnsbl, host_name, dnsbl_ip, inet_ntoa(dnsbl_result));
@@ -2502,7 +2555,7 @@ static void smtp_thread(void* arg)
 	client.time=time32(NULL);
 	SAFECOPY(client.addr,host_ip);
 	SAFECOPY(client.host,host_name);
-	client.port=ntohs(smtp.client_addr.sin_port);
+	client.port=inet_addrport(&smtp.client_addr);
 	client.protocol="SMTP";
 	client.user="<unknown>";
 	client_on(socket,&client,FALSE /* update */);
@@ -2513,7 +2566,7 @@ static void smtp_thread(void* arg)
 	if(startup->login_attempt_throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &smtp.client_addr)) > 1) {
 		lprintf(LOG_DEBUG,"%04d SMTP Throttling suspicious connection from: %s (%u login attempts)"
-			,socket, inet_ntoa(smtp.client_addr.sin_addr), login_attempts);
+			,socket, host_ip, login_attempts);
 		mswait(login_attempts*startup->login_attempt_throttle);
 	}
 
@@ -2573,11 +2626,33 @@ static void smtp_thread(void* arg)
 				if(telegram==TRUE) {		/* Telegram */
 					const char* head="\1n\1h\1cInstant Message\1n from \1h\1y";
 					const char* tail="\1n:\r\n\1h";
+					struct addrinfo ai;
+					struct addrinfo *res,*cur;
+					BOOL matched=FALSE;
+
 					rewind(msgtxt);
 					length=filelength(fileno(msgtxt));
 					
 					p=strchr(sender_addr,'@');
-					if(p==NULL || resolve_ip(p+1)!=smtp.client_addr.sin_addr.s_addr) 
+					memset(&ai, 0, sizeof(ai));
+					ai.ai_flags = AI_PASSIVE;
+					ai.ai_family = smtp.client_addr.addr.sa_family;
+					if(getaddrinfo(p+1, NULL, &ai, &res) != 0)
+						p=NULL;
+					else {
+						for(cur=res; cur; cur=cur->ai_next) {
+							char cur_ip[INET6_ADDRSTRLEN];
+
+							if(inet_addrtop((void *)cur->ai_addr, cur_ip, sizeof(cur_ip))) {
+								if(strcmp(host_ip, cur_ip)==0)
+									matched=TRUE;
+							}
+						}
+						freeaddrinfo(res);
+						if(!matched)
+							p=NULL;
+					}
+					if(p==NULL)
 						/* Append real IP and hostname if different */
 						safe_snprintf(str,sizeof(str),"%s%s\r\n\1w[\1n%s\1h] (\1n%s\1h)%s"
 							,head,sender_addr,host_ip,host_name,tail);
@@ -3097,12 +3172,16 @@ static void smtp_thread(void* arg)
 					}
 
 					snprintf(hdrfield,sizeof(hdrfield),
-						"from %s (%s [%s])\r\n"
-						"          by %s [%s] (%s %s-%s) with %s\r\n"
+						"from %s (%s [%s%s])\r\n"
+						"          by %s [%s%s] (%s %s-%s) with %s\r\n"
 						"          for %s; %s\r\n"
 						"          (envelope-from %s)"
-						,host_name,hello_name,host_ip
-						,startup->host_name,inet_ntoa(server_addr.sin_addr)
+						,host_name,hello_name
+						,smtp.client_addr.addr.sa_family==AF_INET6?"IPv6: ":""
+						,host_ip
+						,startup->host_name
+						,server_addr.addr.sa_family==AF_INET6?"IPv6: ":""
+						,server_ip
 						,server_name
 						,revision,PLATFORM_DESC
 						,esmtp ? "ESMTP" : "SMTP"
@@ -3722,9 +3801,8 @@ static void smtp_thread(void* arg)
 
 			tp=strrchr(p,'@');
 			if(cmd==SMTP_CMD_MAIL && tp!=NULL) {
-				
 				/* RELAY */
-				dest_port=server_addr.sin_port;
+				dest_port=inet_addrport(&server_addr);
 				SAFECOPY(dest_host,tp+1);
 				cp=strrchr(dest_host,':');
 				if(cp!=NULL) {
@@ -3735,12 +3813,12 @@ static void smtp_thread(void* arg)
 				if((stricmp(dest_host,scfg.sys_inetaddr)!=0
 						&& stricmp(dest_host,startup->host_name)!=0
 						&& findstr(dest_host,domain_list)==FALSE)
-					|| dest_port!=server_addr.sin_port) {
+					|| dest_port!=inet_addrport(&server_addr)) {
 
 					SAFEPRINTF(relay_list,"%srelay.cfg",scfg.ctrl_dir);
 					if(relay_user.number==0 /* not authenticated, search for IP */
 						&& startup->options&MAIL_OPT_SMTP_AUTH_VIA_IP) { 
-						relay_user.number=userdatdupe(&scfg, 0, U_NOTE, LEN_NOTE, host_ip, /* del */FALSE, /* next */FALSE);
+						relay_user.number=userdatdupe(&scfg, 0, U_IPADDR, LEN_IPADDR, host_ip, /* del */FALSE, /* next */FALSE);
 						if(relay_user.number) {
 							getuserdat(&scfg,&relay_user);
 							if(relay_user.laston < time(NULL)-(60*60))	/* logon in past hour? */
@@ -4235,6 +4313,7 @@ void get_dns_server(char* dns_server, size_t len)
 	}
 }
 
+/* TODO: IPv6 etc. */
 #ifdef __BORLANDC__
 #pragma argsused
 #endif
@@ -4273,6 +4352,7 @@ static void sendmail_thread(void* arg)
 	SOCKET		sock=INVALID_SOCKET;
 	SOCKADDR_IN	addr;
 	SOCKADDR_IN	server_addr;
+	char		server_ip[INET6_ADDRSTRLEN];
 	time_t		last_scan=0;
 	smb_t		smb;
 	smbmsg_t	msg;
@@ -4295,7 +4375,7 @@ static void sendmail_thread(void* arg)
 
 	listInit(&failed_server_list, /* flags: */0);
 
-	while(server_socket!=INVALID_SOCKET && !terminate_sendmail) {
+	while((!terminated) && !terminate_sendmail) {
 
 		if(startup->options&MAIL_OPT_NO_SENDMAIL) {
 			sem_trywait_block(&sendmail_wakeup_sem,1000);
@@ -4349,7 +4429,7 @@ static void sendmail_thread(void* arg)
 			if(active_sendmail!=0)
 				active_sendmail=0, update_clients();
 
-			if(server_socket==INVALID_SOCKET || terminate_sendmail)	/* server stopped */
+			if(terminated || terminate_sendmail)	/* server stopped */
 				break;
 
 			if(sock!=INVALID_SOCKET) {
@@ -4449,14 +4529,15 @@ static void sendmail_thread(void* arg)
 					|| findstr(p,domain_list)) {
 				/* This is a local message... no need to send to remote */
 				port = startup->smtp_port;
-				if(startup->interface_addr==0)
+				/* TODO: IPv6 */
+				if(startup->outgoing4.s_addr==0)
 					server="127.0.0.1";
 				else {
 					SAFEPRINTF4(numeric_ip, "%u.%u.%u.%u"
-							, startup->interface_addr >> 24
-							, (startup->interface_addr >> 16) & 0xff
-							, (startup->interface_addr >> 8) & 0xff
-							, startup->interface_addr & 0xff);
+							, startup->outgoing4.s_addr >> 24
+							, (startup->outgoing4.s_addr >> 16) & 0xff
+							, (startup->outgoing4.s_addr >> 8) & 0xff
+							, startup->outgoing4.s_addr & 0xff);
 					server = numeric_ip;
 				}
 				sending_locally=TRUE;
@@ -4498,11 +4579,12 @@ static void sendmail_thread(void* arg)
 			if(!port)
 				port=IPPORT_SMTP;
 
-			if((sock=mail_open_socket(SOCK_STREAM,"smtp|sendmail"))==INVALID_SOCKET) {
+			if((sock=socket(AF_INET, SOCK_STREAM, IPPROTO_IP))==INVALID_SOCKET) {
 				remove_msg_intransit(&smb,&msg);
 				lprintf(LOG_ERR,"0000 !SEND ERROR %d opening socket", ERROR_VALUE);
 				continue;
 			}
+			mail_open_socket(sock,"smtp|sendmail");
 
 			if(startup->connect_timeout) {	/* Use non-blocking socket */
 				long nbio=1;
@@ -4515,16 +4597,10 @@ static void sendmail_thread(void* arg)
 			}
 
 			memset(&addr,0,sizeof(addr));
-			addr.sin_addr.s_addr = htonl(startup->interface_addr);
+			addr.sin_addr.s_addr = htonl(startup->outgoing4.s_addr);
 			addr.sin_family = AF_INET;
 
-			/* Not needed.  Port is zero
-			if(startup->seteuid!=NULL)
-				startup->seteuid(FALSE); */
 			i=bind(sock,(struct sockaddr *)&addr, sizeof(addr));
-			/* Not needed.  Port is zero
-			if(startup->seteuid!=NULL)
-				startup->seteuid(TRUE); */
 			if(i!=0) {
 				remove_msg_intransit(&smb,&msg);
 				lprintf(LOG_ERR,"%04d !SEND ERROR %d (%d) binding socket", sock, i, ERROR_VALUE);
@@ -4555,13 +4631,14 @@ static void sendmail_thread(void* arg)
 				server_addr.sin_addr.s_addr = ip_addr;
 				server_addr.sin_family = AF_INET;
 				server_addr.sin_port = htons(port);
+				inet_addrtop(&server_addr,server_ip,sizeof(server_ip));
 
 				if((node=listFindNode(&failed_server_list,&server_addr,sizeof(server_addr))) != NULL) {
 					lprintf(LOG_INFO,"%04d SEND skipping failed SMTP server: Error %d connecting to port %u on %s [%s]"
 					,sock
 					,node->tag
-					,ntohs(server_addr.sin_port)
-					,server,inet_ntoa(server_addr.sin_addr));
+					,inet_addrport(&server_addr)
+					,server,server_ip);
 					SAFEPRINTF2(err,"Error %d connecting to SMTP server: %s"
 						,node->tag, server);
 					continue;
@@ -4570,14 +4647,14 @@ static void sendmail_thread(void* arg)
 				if((server==mx || server==mx2) 
 					&& ((ip_addr&0xff)==127 || ip_addr==0)) {
 					SAFEPRINTF2(err,"Bad IP address (%s) for MX server: %s"
-						,inet_ntoa(server_addr.sin_addr),server);
+						,server_ip,server);
 					continue;
 				}
 				
 				lprintf(LOG_INFO,"%04d SEND connecting to port %u on %s [%s]"
 					,sock
-					,ntohs(server_addr.sin_port)
-					,server,inet_ntoa(server_addr.sin_addr));
+					,inet_addrport(&server_addr)
+					,server,server_ip);
 				if((i=nonblocking_connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr), startup->connect_timeout))!=0) {
 					lprintf(LOG_WARNING,"%04d !SEND ERROR %d connecting to SMTP server: %s"
 						,sock
@@ -4804,7 +4881,7 @@ static void sendmail_thread(void* arg)
 
 void DLLCALL mail_terminate(void)
 {
-  	lprintf(LOG_INFO,"%04d Mail Server terminate",server_socket);
+  	lprintf(LOG_INFO,"Mail Server terminate");
 	terminate_server=TRUE;
 }
 
@@ -4834,20 +4911,9 @@ static void cleanup(int code)
 		FREE_AND_NULL(mailproc_list);
 	}
 
-	if(server_socket!=INVALID_SOCKET) {
-		mail_close_socket(server_socket);
-		server_socket=INVALID_SOCKET;
-	}
-
-	if(submission_socket!=INVALID_SOCKET) {
-		mail_close_socket(submission_socket);
-		submission_socket=INVALID_SOCKET;
-	}
-
-	if(pop3_socket!=INVALID_SOCKET) {
-		mail_close_socket(pop3_socket);
-		pop3_socket=INVALID_SOCKET;
-	}
+	xpms_destroy(mail_set, mail_close_socket_cb, NULL);
+	mail_set=NULL;
+	terminated=TRUE;
 
 	update_clients();	/* active_clients is destroyed below */
 
@@ -4921,23 +4987,21 @@ void DLLCALL mail_server(void* arg)
 	char			str[256];
 	char			error[256];
 	char			compiler[32];
-	SOCKADDR_IN		server_addr;
-	SOCKADDR_IN		client_addr;
+	union xp_sockaddr	client_addr;
 	socklen_t		client_addr_len;
+	char			host_ip[INET6_ADDRSTRLEN];
 	SOCKET			client_socket;
 	int				i;
-	int				result;
 	ulong			l;
 	time_t			t;
 	time_t			start;
 	time_t			initialized=0;
-	fd_set			socket_set;
-	SOCKET			high_socket_set;
 	pop3_t*			pop3;
 	smtp_t*			smtp;
-	struct timeval	tv;
 	FILE*			fp;
 	str_list_t		sec_list;
+	struct in_addr	iaddr;
+	void			*cbdata;
 
 	mail_ver();
 
@@ -4982,7 +5046,8 @@ void DLLCALL mail_server(void* arg)
 	js_server_props.version_detail=mail_ver();
 	js_server_props.clients=&active_clients.value;
 	js_server_props.options=&startup->options;
-	js_server_props.interface_addr=&startup->interface_addr;
+	/* TODO: IPv6 */
+	js_server_props.interface_addr=&startup->outgoing4;
 
 	uptime=0;
 	memset(&stats,0,sizeof(stats));
@@ -5122,152 +5187,28 @@ void DLLCALL mail_server(void* arg)
 		update_clients();
 
 		/* open a socket and wait for a client */
-
-		server_socket = mail_open_socket(SOCK_STREAM,"smtp");
-
-		if(server_socket == INVALID_SOCKET) {
-			lprintf(LOG_CRIT,"!ERROR %d opening socket", ERROR_VALUE);
+		mail_set = xpms_create(startup->bind_retry_count, startup->bind_retry_delay, lprintf);
+		if(mail_set == NULL) {
+			lprintf(LOG_CRIT,"!ERROR creating mail server socket set", ERROR_VALUE);
 			cleanup(1);
 			return;
 		}
-
-		lprintf(LOG_DEBUG,"%04d SMTP socket opened",server_socket);
-
-		/*****************************/
-		/* Listen for incoming calls */
-		/*****************************/
-		memset(&server_addr, 0, sizeof(server_addr));
-
-		server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
-		server_addr.sin_family = AF_INET;
-		server_addr.sin_port   = htons(startup->smtp_port);
-
-		if(startup->smtp_port < IPPORT_RESERVED) {
-			if(startup->seteuid!=NULL)
-				startup->seteuid(FALSE);
-		}
-		result = retry_bind(server_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
-			,startup->bind_retry_count,startup->bind_retry_delay,"SMTP Server",lprintf);
-		if(startup->smtp_port < IPPORT_RESERVED) {
-			if(startup->seteuid!=NULL)
-				startup->seteuid(TRUE);
-		}
-		if(result != 0) {
-			lprintf(LOG_CRIT,"%04d %s",server_socket, BIND_FAILURE_HELP);
-			cleanup(1);
-			return;
-		}
-		result = listen(server_socket, 1);
-
-		if(result != 0) {
-			lprintf(LOG_CRIT,"%04d !ERROR %d (%d) listening on SMTP socket"
-				,server_socket, result, ERROR_VALUE);
-			cleanup(1);
-			return;
-		}
-		lprintf(LOG_INFO,"%04d SMTP Server listening on port %u"
-			,server_socket, startup->smtp_port);
+		if(!xpms_add_list(mail_set, PF_UNSPEC, SOCK_STREAM, 0, startup->interfaces, startup->smtp_port, "SMTP Server", mail_open_socket, startup->seteuid, "smtp"))
+			lprintf(LOG_INFO,"SMTP No extra interfaces listening");
+		lprintf(LOG_INFO,"SMTP Server listening");
 
 		if(startup->options&MAIL_OPT_USE_SUBMISSION_PORT) {
-
-			submission_socket = mail_open_socket(SOCK_STREAM,"submission");
-
-			if(submission_socket == INVALID_SOCKET) {
-				lprintf(LOG_CRIT,"!ERROR %d opening socket", ERROR_VALUE);
-				cleanup(1);
-				return;
-			}
-
-			lprintf(LOG_DEBUG,"%04d SUBMISSION socket opened",submission_socket);
-
-			/*****************************/
-			/* Listen for incoming calls */
-			/*****************************/
-			memset(&server_addr, 0, sizeof(server_addr));
-
-			server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
-			server_addr.sin_family = AF_INET;
-			server_addr.sin_port   = htons(startup->submission_port);
-
-			if(startup->submission_port < IPPORT_RESERVED) {
-				if(startup->seteuid!=NULL)
-					startup->seteuid(FALSE);
-			}
-			result = retry_bind(submission_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
-				,startup->bind_retry_count,startup->bind_retry_delay,"SMTP Submission Agent",lprintf);
-			if(startup->submission_port < IPPORT_RESERVED) {
-				if(startup->seteuid!=NULL)
-					startup->seteuid(TRUE);
-			}
-			if(result != 0) {
-				lprintf(LOG_CRIT,"%04d %s",submission_socket, BIND_FAILURE_HELP);
-				cleanup(1);
-				return;
-			}
-
-			result = listen(submission_socket, 1);
-
-			if(result != 0) {
-				lprintf(LOG_CRIT,"%04d !ERROR %d (%d) listening on SUBMISSION socket"
-					,submission_socket, result, ERROR_VALUE);
-				cleanup(1);
-				return;
-			}
-
-			lprintf(LOG_INFO,"%04d SUBMISSION Server listening on port %u"
-				,submission_socket, startup->submission_port);
+			if(xpms_add_list(mail_set, PF_UNSPEC, SOCK_STREAM, 0, startup->interfaces, startup->submission_port, "SMTP Submission Agent", mail_open_socket, startup->seteuid, "submission"))
+				lprintf(LOG_INFO,"SUBMISSION Server listening on port %u"
+					,startup->submission_port);
 		}
 
 		if(startup->options&MAIL_OPT_ALLOW_POP3) {
 
 			/* open a socket and wait for a client */
-
-			pop3_socket = mail_open_socket(SOCK_STREAM,"pop3");
-
-			if(pop3_socket == INVALID_SOCKET) {
-				lprintf(LOG_CRIT,"!ERROR %d opening POP3 socket", ERROR_VALUE);
-				cleanup(1);
-				return;
-			}
-
-			lprintf(LOG_DEBUG,"%04d POP3 socket opened",pop3_socket);
-
-			/*****************************/
-			/* Listen for incoming calls */
-			/*****************************/
-			memset(&server_addr, 0, sizeof(server_addr));
-
-			server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
-			server_addr.sin_family = AF_INET;
-			server_addr.sin_port   = htons(startup->pop3_port);
-
-			if(startup->pop3_port < IPPORT_RESERVED) {
-				if(startup->seteuid!=NULL)
-					startup->seteuid(FALSE);
-			}
-			result = retry_bind(pop3_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
-				,startup->bind_retry_count,startup->bind_retry_delay,"POP3 Server",lprintf);
-			if(startup->pop3_port < IPPORT_RESERVED) {
-				if(startup->seteuid!=NULL)
-					startup->seteuid(FALSE);
-			}
-			if(result != 0) {
-				lprintf(LOG_CRIT,"%04d %s",pop3_socket,BIND_FAILURE_HELP);
-				cleanup(1);
-				return;
-			}
-
-			result = listen(pop3_socket, 1);
-
-			if(result != 0) {
-				lprintf(LOG_CRIT,"%04d !ERROR %d (%d) listening on POP3 socket"
-					,pop3_socket, result, ERROR_VALUE);
-				cleanup(1);
-				return;
-			}
-
-			lprintf(LOG_INFO,"%04d POP3 Server listening on port %u"
-				,pop3_socket, startup->pop3_port);
+			if(!xpms_add_list(mail_set, PF_UNSPEC, SOCK_STREAM, 0, startup->pop3_interfaces, startup->pop3_port, "POP3 Server", mail_open_socket, startup->seteuid, "pop3"))
+				lprintf(LOG_INFO,"SMTP No extra interfaces listening");
+			lprintf(LOG_INFO,"POP3 Server listening");
 		}
 
 		sem_init(&sendmail_wakeup_sem,0,0);
@@ -5295,28 +5236,26 @@ void DLLCALL mail_server(void* arg)
 		if(startup->started!=NULL)
     		startup->started(startup->cbdata);
 
-		lprintf(LOG_INFO,"%04d Mail Server thread started",server_socket);
+		lprintf(LOG_INFO,"Mail Server thread started");
 
-		while(server_socket!=INVALID_SOCKET && !terminate_server) {
+		while(!terminated && !terminate_server) {
 
 			if(protected_uint32_value(thread_count) <= 1+sendmail_running) {
 				if(!(startup->options&MAIL_OPT_NO_RECYCLE)) {
 					if((p=semfile_list_check(&initialized,recycle_semfiles))!=NULL) {
-						lprintf(LOG_INFO,"%04d Recycle semaphore file (%s) detected"
-							,server_socket,p);
+						lprintf(LOG_INFO,"Recycle semaphore file (%s) detected",p);
 						break;
 					}
 					if(startup->recycle_now==TRUE) {
-						lprintf(LOG_NOTICE,"%04d Recycle semaphore signaled", server_socket);
+						lprintf(LOG_NOTICE,"Recycle semaphore signaled");
 						startup->recycle_now=FALSE;
 						break;
 					}
 				}
 				if(((p=semfile_list_check(&initialized,shutdown_semfiles))!=NULL
-						&& lprintf(LOG_INFO,"%04d Shutdown semaphore file (%s) detected"
-						,server_socket,p))
+						&& lprintf(LOG_INFO,"Shutdown semaphore file (%s) detected",p))
 					|| (startup->shutdown_now==TRUE
-						&& lprintf(LOG_INFO,"%04d Shutdown semaphore signaled",server_socket))) {
+						&& lprintf(LOG_INFO,"Shutdown semaphore signaled"))) {
 					startup->shutdown_now=FALSE;
 					terminate_server=TRUE;
 					break;
@@ -5324,145 +5263,23 @@ void DLLCALL mail_server(void* arg)
 			}
 
 			/* now wait for connection */
-
-			FD_ZERO(&socket_set);
-			FD_SET(server_socket,&socket_set);
-			high_socket_set=server_socket+1;
-			if(startup->options&MAIL_OPT_ALLOW_POP3 
-				&& pop3_socket!=INVALID_SOCKET) {
-				FD_SET(pop3_socket,&socket_set);
-				if(pop3_socket+1>high_socket_set)
-					high_socket_set=pop3_socket+1;
-			}
-			if(startup->options&MAIL_OPT_USE_SUBMISSION_PORT 
-				&& submission_socket!=INVALID_SOCKET) {
-				FD_SET(submission_socket,&socket_set);
-				if(submission_socket+1>high_socket_set)
-					high_socket_set=submission_socket+1;
-			}
-
-			tv.tv_sec=startup->sem_chk_freq;
-			tv.tv_usec=0;
-
-			if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) {
-				if(i==0)
-					continue;
-				if(ERROR_VALUE==EINTR)
-					lprintf(LOG_DEBUG,"%04d Mail Server listening interrupted",server_socket);
-				else if(ERROR_VALUE == ENOTSOCK)
-            		lprintf(LOG_NOTICE,"%04d Mail Server sockets closed",server_socket);
-				else
-					lprintf(LOG_WARNING,"%04d !ERROR %d selecting sockets",server_socket,ERROR_VALUE);
-				continue;
-			}
-
-			if(server_socket!=INVALID_SOCKET && !terminate_server
-				&& (FD_ISSET(server_socket,&socket_set) 
-					|| (startup->options&MAIL_OPT_USE_SUBMISSION_PORT
-						&& FD_ISSET(submission_socket,&socket_set)))) {
-
-				client_addr_len = sizeof(client_addr);
-				client_socket = accept(
-					FD_ISSET(server_socket,&socket_set) ? server_socket:submission_socket
-					,(struct sockaddr *)&client_addr
-        			,&client_addr_len);
-
-				if(client_socket == INVALID_SOCKET)
-				{
-#if 0	/* is this necessary still? */
-					if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINVAL) {
-            			lprintf(LOG_NOTICE,"%04d SMTP socket closed while listening"
-							,server_socket);
-						break;
-					}
-#endif
-					lprintf(LOG_WARNING,"%04d SMTP !ERROR %d accepting connection"
-						,FD_ISSET(server_socket,&socket_set) ? server_socket:submission_socket
-						,ERROR_VALUE);
-#ifdef _WIN32
-					if(WSAGetLastError()==WSAENOBUFS)	/* recycle (re-init WinSock) on this error */
-						break;
-#endif
-					continue;
-				}
+			client_addr_len = sizeof(client_addr);
+			client_socket = xpms_accept(mail_set,&client_addr, &client_addr_len, startup->sem_chk_freq*1000, &cbdata);
+			if(client_socket != INVALID_SOCKET) {
 				if(startup->socket_open!=NULL)
 					startup->socket_open(startup->cbdata,TRUE);
 				stats.sockets++;
 
-				if(trashcan(&scfg,inet_ntoa(client_addr.sin_addr),"ip-silent")) {
+				inet_addrtop(&client_addr, host_ip, sizeof(host_ip));
+				if(trashcan(&scfg,host_ip,"ip-silent")) {
 					mail_close_socket(client_socket);
 					stats.connections_ignored++;
 					continue;
 				}
 
 				if(protected_uint32_value(active_clients)>=startup->max_clients) {
-					lprintf(LOG_WARNING,"%04d SMTP !MAXIMUM CLIENTS (%u) reached, access denied (%u total)"
-						,client_socket, startup->max_clients, ++stats.connections_refused);
-					sockprintf(client_socket,"421 Maximum active clients reached, please try again later.");
-					mswait(3000);
-					mail_close_socket(client_socket);
-					continue;
-				}
-
-				l=1;
-
-				if((i=ioctlsocket(client_socket, FIONBIO, &l))!=0) {
-					lprintf(LOG_CRIT,"%04d SMTP !ERROR %d (%d) disabling blocking on socket"
-						,client_socket, i, ERROR_VALUE);
-					mail_close_socket(client_socket);
-					continue;
-				}
-
-				if((smtp=malloc(sizeof(smtp_t)))==NULL) {
-					lprintf(LOG_CRIT,"%04d SMTP !ERROR allocating %u bytes of memory for smtp_t"
-						,client_socket, sizeof(smtp_t));
-					mail_close_socket(client_socket);
-					continue;
-				}
-
-				smtp->socket=client_socket;
-				smtp->client_addr=client_addr;
-				protected_uint32_adjust(&thread_count,1);
-				_beginthread(smtp_thread, 0, smtp);
-				stats.connections_served++;
-			}
-
-			if(pop3_socket!=INVALID_SOCKET
-				&& FD_ISSET(pop3_socket,&socket_set)) {
-
-				client_addr_len = sizeof(client_addr);
-				client_socket = accept(pop3_socket, (struct sockaddr *)&client_addr
-        			,&client_addr_len);
-
-				if(client_socket == INVALID_SOCKET)
-				{
-#if 0	/* is this necessary still? */
-					if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINVAL) {
-            			lprintf(LOG_NOTICE,"%04d POP3 socket closed while listening",pop3_socket);
-						break;
-					}
-#endif
-					lprintf(LOG_WARNING,"%04d POP3 !ERROR %d accepting connection"
-						,pop3_socket, ERROR_VALUE);
-#ifdef _WIN32
-					if(WSAGetLastError()==WSAENOBUFS)	/* recycle (re-init WinSock) on this error */
-						break;
-#endif
-					continue;
-				}
-				if(startup->socket_open!=NULL)
-					startup->socket_open(startup->cbdata,TRUE);
-				stats.sockets++;
-
-				if(trashcan(&scfg,inet_ntoa(client_addr.sin_addr),"ip-silent")) {
-					mail_close_socket(client_socket);
-					stats.connections_ignored++;
-					continue;
-				}
-
-				if(protected_uint32_value(active_clients)>=startup->max_clients) {
-					lprintf(LOG_WARNING,"%04d POP3 !MAXIMUM CLIENTS (%u) reached, access denied (%u total)"
-						,client_socket, startup->max_clients, ++stats.connections_refused);
+					lprintf(LOG_WARNING,"%04d %s !MAXIMUM CLIENTS (%u) reached, access denied (%u total)"
+						,client_socket, (char *)cbdata, startup->max_clients, ++stats.connections_refused);
 					sockprintf(client_socket,"-ERR Maximum active clients reached, please try again later.");
 					mswait(3000);
 					mail_close_socket(client_socket);
@@ -5472,39 +5289,57 @@ void DLLCALL mail_server(void* arg)
 				l=1;
 
 				if((i=ioctlsocket(client_socket, FIONBIO, &l))!=0) {
-					lprintf(LOG_CRIT,"%04d POP3 !ERROR %d (%d) disabling blocking on socket"
-						,client_socket, i, ERROR_VALUE);
+					lprintf(LOG_CRIT,"%04d %s !ERROR %d (%d) disabling blocking on socket"
+						,client_socket, (char *)cbdata, i, ERROR_VALUE);
 					sockprintf(client_socket,"-ERR System error, please try again later.");
 					mswait(3000);
 					mail_close_socket(client_socket);
 					continue;
 				}
 
-				if((pop3=malloc(sizeof(pop3_t)))==NULL) {
-					lprintf(LOG_CRIT,"%04d POP3 !ERROR allocating %u bytes of memory for pop3_t"
-						,client_socket,sizeof(pop3_t));
-					sockprintf(client_socket,"-ERR System error, please try again later.");
-					mswait(3000);
-					mail_close_socket(client_socket);
-					continue;
+				if(strcmp((char *)cbdata, "pop3")) { /* Not POP3 */
+					if((smtp=malloc(sizeof(smtp_t)))==NULL) {
+						lprintf(LOG_CRIT,"%04d SMTP !ERROR allocating %u bytes of memory for smtp_t"
+							,client_socket, sizeof(smtp_t));
+						mail_close_socket(client_socket);
+						continue;
+					}
+
+					smtp->socket=client_socket;
+					memcpy(&smtp->client_addr, &client_addr, client_addr_len);
+					smtp->client_addr_len=client_addr_len;
+					protected_uint32_adjust(&thread_count,1);
+					_beginthread(smtp_thread, 0, smtp);
+					stats.connections_served++;
 				}
+				else {
+					if((pop3=malloc(sizeof(pop3_t)))==NULL) {
+						lprintf(LOG_CRIT,"%04d POP3 !ERROR allocating %u bytes of memory for pop3_t"
+							,client_socket,sizeof(pop3_t));
+						sockprintf(client_socket,"-ERR System error, please try again later.");
+						mswait(3000);
+						mail_close_socket(client_socket);
+						continue;
+					}
 
-				pop3->socket=client_socket;
-				pop3->client_addr=client_addr;
-				protected_uint32_adjust(&thread_count,1);
-				_beginthread(pop3_thread, 0, pop3);
-				stats.connections_served++;
+					pop3->socket=client_socket;
+					memcpy(&pop3->client_addr, &client_addr, client_addr_len);
+					pop3->client_addr_len=client_addr_len;
+					protected_uint32_adjust(&thread_count,1);
+					_beginthread(pop3_thread, 0, pop3);
+					stats.connections_served++;
+				}
 			}
 		}
 
 		if(protected_uint32_value(active_clients)) {
-			lprintf(LOG_DEBUG,"%04d Waiting for %d active clients to disconnect..."
-				,server_socket, protected_uint32_value(active_clients));
+			lprintf(LOG_DEBUG,"Waiting for %d active clients to disconnect..."
+				, protected_uint32_value(active_clients));
 			start=time(NULL);
 			while(protected_uint32_value(active_clients)) {
 				if(startup->max_inactivity && time(NULL)-start>startup->max_inactivity) {
-					lprintf(LOG_WARNING,"%04d !TIMEOUT (%u seconds) waiting for %d active clients"
-						,server_socket, startup->max_inactivity, protected_uint32_value(active_clients));
+					lprintf(LOG_WARNING,"!TIMEOUT (%u seconds) waiting for %d active clients"
+						, startup->max_inactivity, protected_uint32_value(active_clients));
 					break;
 				}
 				mswait(100);
@@ -5517,13 +5352,11 @@ void DLLCALL mail_server(void* arg)
 			mswait(100);
 		}
 		if(sendmail_running) {
-			lprintf(LOG_DEBUG,"%04d Waiting for SendMail thread to terminate..."
-				,server_socket);
+			lprintf(LOG_DEBUG,"Waiting for SendMail thread to terminate...");
 			start=time(NULL);
 			while(sendmail_running) {
 				if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
-					lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for sendmail thread to terminate"
-						,server_socket);
+					lprintf(LOG_WARNING,"!TIMEOUT waiting for sendmail thread to terminate");
 					break;
 				}
 				mswait(500);
diff --git a/src/sbbs3/mailsrvr.h b/src/sbbs3/mailsrvr.h
index 7853bee7396c514b74e70aa85f40c9234dd39f61..5e53521d4141cfa5a8454ce1c1e564dfad07ba5f 100644
--- a/src/sbbs3/mailsrvr.h
+++ b/src/sbbs3/mailsrvr.h
@@ -61,7 +61,10 @@ typedef struct {
 	WORD	max_recipients;
 #define MAIL_DEFAULT_MAX_RECIPIENTS			100
 	WORD	sem_chk_freq;		/* semaphore file checking frequency (in seconds) */
-    DWORD   interface_addr;
+	struct in_addr outgoing4;
+	struct in6_addr	outgoing6;
+    str_list_t   interfaces;
+    str_list_t   pop3_interfaces;
     DWORD	options;			/* See MAIL_OPT definitions */
     DWORD	max_msg_size;		/* Max msg size in bytes (0=unlimited) */
 #define MAIL_DEFAULT_MAX_MSG_SIZE			(20*1024*1024)	/* 20MB */
@@ -130,7 +133,7 @@ typedef struct {
 static struct init_field mail_init_fields[] = { 
 	 OFFSET_AND_SIZE(mail_startup_t,smtp_port)
 	,OFFSET_AND_SIZE(mail_startup_t,pop3_port)
-	,OFFSET_AND_SIZE(mail_startup_t,interface_addr)
+	,OFFSET_AND_SIZE(mail_startup_t,interfaces)
 	,OFFSET_AND_SIZE(mail_startup_t,ctrl_dir)
 	,{ 0,0 }	/* terminator */
 };
diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp
index 69744fbe28d12ca1b25f314cfaeac2d82a307f50..1e51ca479291171bd02b658f74a0f149f969fa8f 100644
--- a/src/sbbs3/main.cpp
+++ b/src/sbbs3/main.cpp
@@ -42,13 +42,11 @@
 #include "js_rtpool.h"
 #include "js_request.h"
 #include "js_socket.h"
+#include <multisock.h>
+#include <limits.h>		// HOST_NAME_MAX
 
 #ifdef __unix__
 	#include <sys/un.h>
-	#ifndef SUN_LEN
-		#define SUN_LEN(su) \
-	        (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
-	#endif
 #endif
 
 //#define SBBS_TELNET_ENVIRON_SUPPORT 1
@@ -90,11 +88,7 @@ SOCKET	spy_socket[MAX_NODES];
 SOCKET	uspy_socket[MAX_NODES];	  /* UNIX domain spy sockets */
 #endif
 SOCKET	node_socket[MAX_NODES];
-static	SOCKET telnet_socket=INVALID_SOCKET;
-static	SOCKET rlogin_socket=INVALID_SOCKET;
-#ifdef USE_CRYPTLIB
-static	SOCKET ssh_socket=INVALID_SOCKET;
-#endif
+struct xpms_set				*ts_set;
 static	sbbs_t*	sbbs=NULL;
 static	scfg_t	scfg;
 static	char *	text[TOTAL_TEXT];
@@ -202,6 +196,30 @@ int eprintf(int level, const char *fmt, ...)
     return(startup->event_lputs(startup->event_cbdata,level,sbuf));
 }
 
+struct main_sock_cb_data {
+	bbs_startup_t	*startup;
+	const char		*protocol;
+};
+
+void sock_cb(SOCKET sock, void *cb_data)
+{
+	char	error_str[256];
+	struct main_sock_cb_data *cb=(struct main_sock_cb_data *)cb_data;
+
+	if(cb->startup && cb->startup->socket_open)
+		cb->startup->socket_open(cb->startup->cbdata, TRUE);
+	if(set_socket_options(&scfg, sock, cb->protocol, error_str, sizeof(error_str)))
+		lprintf(LOG_ERR,"%04d !ERROR %s",sock,error_str);
+}
+
+void sock_close_cb(SOCKET sock, void *cb_data)
+{
+	bbs_startup_t	*su=(bbs_startup_t *)cb_data;
+
+	if(su && su->socket_open)
+		su->socket_open(su->cbdata, FALSE);
+}
+
 SOCKET open_socket(int type, const char* protocol)
 {
 	SOCKET	sock;
@@ -216,12 +234,12 @@ SOCKET open_socket(int type, const char* protocol)
 	return(sock);
 }
 
-SOCKET accept_socket(SOCKET s, SOCKADDR* addr, socklen_t* addrlen)
+SOCKET accept_socket(SOCKET s, union xp_sockaddr* addr, socklen_t* addrlen)
 {
 	SOCKET	sock;
 
-	sock=accept(s,addr,addrlen);
-	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
+	sock=accept(s,&addr->addr,addrlen);
+	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL)
 		startup->socket_open(startup->cbdata,TRUE);
 
 	return(sock);
@@ -243,7 +261,7 @@ int close_socket(SOCKET sock)
 	return(result);
 }
 
-
+/* TODO: IPv6 */
 u_long resolve_ip(char *addr)
 {
 	HOSTENT*	host;
@@ -303,7 +321,7 @@ DLLEXPORT void DLLCALL sbbs_srand()
 		rd=read(rf, &seed, sizeof(seed));
 		close(rf);
 	}
-	if(rd != sizeof(seed))
+	if (rd != sizeof(seed))
 #endif
 		seed = time32(NULL) ^ (uintmax_t)GetCurrentThreadId();
 
@@ -441,7 +459,7 @@ DLLCALL js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec
 }
 
 JSBool 
-DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs, BOOL append)
+DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs)
 {
 	int			i;
 	jsuint		len=0;
@@ -450,21 +468,33 @@ DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *fun
 	JSObject*	method;
 	JSObject*	method_array;
 	JSString*	js_str;
+	size_t		str_len=0;
+	char		*str=NULL;
 
 	/* Return existing method_list array if it's already been created */
-	if(JS_GetProperty(cx,obj,method_array_name,&val) && val!=JSVAL_VOID)
+	if(JS_GetProperty(cx,obj,method_array_name,&val) && val!=JSVAL_VOID) {
 		method_array=JSVAL_TO_OBJECT(val);
-	else
+		// If the first item is already in the list, don't do anything.
+		if(!JS_GetArrayLength(cx, method_array, &len))
+			return(JS_FALSE);
+		for(i=0; i<len; i++) {
+			if(JS_GetElement(cx, method_array, i, &val)!=JS_TRUE || val == JSVAL_VOID)
+				continue;
+			JS_GetProperty(cx, JSVAL_TO_OBJECT(val), "name", &val);
+			JSVALUE_TO_RASTRING(cx, val, str, &str_len, NULL);
+			if(str==NULL)
+				continue;
+			if(strcmp(str, funcs[0].name)==0)
+				return(JS_TRUE);
+		}
+	}
+	else {
 		if((method_array=JS_NewArrayObject(cx, 0, NULL))==NULL) 
 			return(JS_FALSE);
-
-	if(!JS_DefineProperty(cx, obj, method_array_name, OBJECT_TO_JSVAL(method_array)
-		, NULL, NULL, 0))
-		return(JS_FALSE);
-
-	if(append)
-		if(!JS_GetArrayLength(cx, method_array, &len))
+		if(!JS_DefineProperty(cx, obj, method_array_name, OBJECT_TO_JSVAL(method_array)
+				, NULL, NULL, 0))
 			return(JS_FALSE);
+	}
 
 	for(i=0;funcs[i].name;i++) {
 
@@ -540,7 +570,7 @@ DLLCALL js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertyS
 	}
 		
 	if(funcs) {
-		if(!js_DefineSyncMethods(cx, obj, funcs, 0))
+		if(!js_DefineSyncMethods(cx, obj, funcs))
 			ret=JS_FALSE;
 	}
 
@@ -569,7 +599,7 @@ DLLCALL js_DefineSyncProperties(JSContext *cx, JSObject *obj, jsSyncPropertySpec
 
 
 JSBool 
-DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs, BOOL append)
+DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec *funcs)
 {
 	uint i;
 
@@ -1154,6 +1184,10 @@ bool sbbs_t::js_init(ulong* stack_frame)
 			break;
 		rooted=true;
 
+#ifdef BUILD_JSDOCS
+		js_CreateUifcObject(js_cx, js_glob);
+#endif
+
 		/* BBS Object */
 		if(js_CreateBbsObject(js_cx, js_glob)==NULL)
 			break;
@@ -2003,7 +2037,7 @@ void output_thread(void* arg)
 	char		node[128];
 	char		stats[128];
     BYTE		buf[IO_THREAD_BUF_SIZE];
-	int			i;
+	int			i=0;	// Assignment to silence Valgrind
     ulong		avail;
 	ulong		total_sent=0;
 	ulong		total_pkts=0;
@@ -2562,7 +2596,7 @@ void event_thread(void* arg)
 						sbbs->console&=~CON_L_ECHO;
 						sbbs->online=FALSE;
 						remove(str);
-					} 
+					}
 				}
 				globfree(&g);
 			}
@@ -2898,7 +2932,7 @@ void event_thread(void* arg)
 
 
 //****************************************************************************
-sbbs_t::sbbs_t(ushort node_num, SOCKADDR_IN addr, const char* name, SOCKET sd,
+sbbs_t::sbbs_t(ushort node_num, union xp_sockaddr *addr, size_t addr_len, const char* name, SOCKET sd,
 			   scfg_t* global_cfg, char* global_text[], client_t* client_info)
 {
 	char	nodestr[32];
@@ -2949,7 +2983,7 @@ sbbs_t::sbbs_t(ushort node_num, SOCKADDR_IN addr, const char* name, SOCKET sd,
 		memset(&client,0,sizeof(client));
 	else
 		memcpy(&client,client_info,sizeof(client));
-	client_addr = addr;
+	client_addr.store = addr->store;
 	client_socket = sd;
 	SAFECOPY(client_name, name);
 	client_socket_dup=INVALID_SOCKET;
@@ -3086,7 +3120,7 @@ bool sbbs_t::init()
 	uint		i,j,k,l;
 	node_t		node;
 	socklen_t	addr_len;
-	SOCKADDR_IN	addr;
+	union xp_sockaddr	addr;
 
 	RingBufInit(&inbuf, IO_THREAD_BUF_SIZE);
 	if(cfg.node_num>0)
@@ -3114,15 +3148,16 @@ bool sbbs_t::init()
 #endif
 
 		addr_len=sizeof(addr);
-		if((result=getsockname(client_socket, (struct sockaddr *)&addr,&addr_len))!=0) {
+		if((result=getsockname(client_socket, &addr.addr, &addr_len))!=0) {
 			lprintf(LOG_ERR,"Node %d !ERROR %d (%d) getting address/port"
 				,cfg.node_num, result, ERROR_VALUE);
 			return(false);
 		} 
+		inet_addrtop(&addr, local_addr, sizeof(local_addr));
+		inet_addrtop(&client_addr, client_ipaddr, sizeof(client_ipaddr));
 		lprintf(LOG_INFO,"Node %d attached to local interface %s port %u"
-			,cfg.node_num, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
+			,cfg.node_num, local_addr, inet_addrport(&addr));
 
-		local_addr=addr.sin_addr.s_addr;
 	}
 
 	if((comspec=os_cmdshell())==NULL) {
@@ -3553,7 +3588,7 @@ void sbbs_t::spymsg(const char* msg)
 		return;
 
 	SAFEPRINTF4(str,"\r\n\r\n*** Spy Message ***\r\nNode %d: %s [%s]\r\n*** %s ***\r\n\r\n"
-		,cfg.node_num,client_name,inet_ntoa(client_addr.sin_addr),msg);
+		,cfg.node_num,client_name,client_ipaddr,msg);
 	if(startup->node_spybuf!=NULL 
 		&& startup->node_spybuf[cfg.node_num-1]!=NULL) {
 		RingBufWrite(startup->node_spybuf[cfg.node_num-1],(uchar*)str,strlen(str));
@@ -3691,6 +3726,11 @@ void sbbs_t::hangup(void)
 	if(client_socket!=INVALID_SOCKET) {
 		mswait(1000);	/* Give socket output buffer time to flush */
 		client_off(client_socket);
+		if(ssh_mode) {
+			pthread_mutex_lock(&sbbs->ssh_mutex);
+			cryptDestroySession(sbbs->ssh_session);
+			pthread_mutex_unlock(&sbbs->ssh_mutex);
+		}
 		close_socket(client_socket);
 		client_socket=INVALID_SOCKET;
 	}
@@ -4006,7 +4046,7 @@ void node_thread(void* arg)
 	if(startup->login_attempt_throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &sbbs->client_addr)) > 1) {
 		lprintf(LOG_DEBUG,"Node %d Throttling suspicious connection from: %s (%u login attempts)"
-			,sbbs->cfg.node_num, inet_ntoa(sbbs->client_addr.sin_addr), login_attempts);
+			,sbbs->cfg.node_num, sbbs->client_ipaddr, login_attempts);
 		mswait(login_attempts*startup->login_attempt_throttle);
 	}
 
@@ -4369,25 +4409,12 @@ void DLLCALL bbs_terminate(void)
 	terminate_server=true;
 }
 
+extern bbs_startup_t bbs_startup;
 static void cleanup(int code)
 {
     lputs(LOG_INFO,"Terminal Server thread terminating");
 
-	if(telnet_socket!=INVALID_SOCKET) {
-		close_socket(telnet_socket);
-		telnet_socket=INVALID_SOCKET;
-	}
-	if(rlogin_socket!=INVALID_SOCKET) {
-		close_socket(rlogin_socket);
-		rlogin_socket=INVALID_SOCKET;
-	}
-#ifdef USE_CRYPTLIB
-	if(ssh_socket!=INVALID_SOCKET) {
-		close_socket(ssh_socket);
-		ssh_socket=INVALID_SOCKET;
-	}
-#endif
-
+	xpms_destroy(ts_set, sock_close_cb, startup);
 
 #ifdef _WINSOCKAPI_
 	if(WSAInitialized && WSACleanup()!=0) 
@@ -4433,17 +4460,15 @@ static void cleanup(int code)
 
 void DLLCALL bbs_thread(void* arg)
 {
-	const char*		host_name;
+	char			host_name[256];
 	char*			identity;
 	char*			p;
     char			str[MAX_PATH+1];
 	char			logstr[256];
-	SOCKADDR_IN		server_addr={0};
-	SOCKADDR_IN		client_addr;
+	union xp_sockaddr	server_addr={0};
+	union xp_sockaddr	client_addr;
 	socklen_t		client_addr_len;
 	SOCKET			client_socket=INVALID_SOCKET;
-	fd_set			socket_set;
-	SOCKET			high_socket_set;
 	int				i;
     int				file;
 	int				result;
@@ -4457,12 +4482,17 @@ void DLLCALL bbs_thread(void* arg)
 	BOOL			is_client=FALSE;
 #ifdef __unix__
 	SOCKET	uspy_listen_socket[MAX_NODES];
-	struct sockaddr_un uspy_addr;
-	socklen_t		uspy_addr_len;
+	struct main_sock_cb_data	uspy_cb[MAX_NODES]={};
+	union xp_sockaddr uspy_addr;
 #endif
 #ifdef USE_CRYPTLIB
 	CRYPT_CONTEXT	ssh_context;
 #endif
+	struct main_sock_cb_data	telnet_cb;
+	struct main_sock_cb_data	ssh_cb;
+	struct main_sock_cb_data	rlogin_cb;
+	void						*ts_cb;
+	struct in_addr				iaddr;
 
     if(startup==NULL) {
     	sbbs_beep(100,500);
@@ -4498,7 +4528,8 @@ void DLLCALL bbs_thread(void* arg)
 	js_server_props.version_detail=bbs_ver();
 	js_server_props.clients=&node_threads_running.value;
 	js_server_props.options=&startup->options;
-	js_server_props.interface_addr=&startup->telnet_interface;
+	/* TODO: IPv6 */
+	js_server_props.interface_addr=(uint32_t *)&startup->outgoing4.s_addr;
 
 	uptime=0;
 	served=0;
@@ -4653,97 +4684,27 @@ void DLLCALL bbs_thread(void* arg)
 	startup->node_inbuf=node_inbuf;
 
     /* open a socket and wait for a client */
-
-    telnet_socket = open_socket(SOCK_STREAM, "telnet");
-
-	if(telnet_socket == INVALID_SOCKET) {
-		lprintf(LOG_CRIT,"!ERROR %d creating Telnet socket", ERROR_VALUE);
+    ts_set = xpms_create(startup->bind_retry_count, startup->bind_retry_delay, lprintf);
+    if(ts_set==NULL) {
+		lprintf(LOG_CRIT,"!ERROR %d creating Terminal Server socket set", ERROR_VALUE);
 		cleanup(1);
 		return;
 	}
+    telnet_cb.protocol="telnet";
+    telnet_cb.startup=startup;
 
-    lprintf(LOG_DEBUG,"Telnet socket %d opened",telnet_socket);
-
-	/*****************************/
-	/* Listen for incoming calls */
-	/*****************************/
-    memset(&server_addr, 0, sizeof(server_addr));
-
-	server_addr.sin_addr.s_addr = htonl(startup->telnet_interface);
-    server_addr.sin_family = AF_INET;
-    server_addr.sin_port   = htons(startup->telnet_port);
-
-	if(startup->telnet_port < IPPORT_RESERVED) {
-		if(startup->seteuid!=NULL)
-			startup->seteuid(FALSE);
-	}
-    result = retry_bind(telnet_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
-		,startup->bind_retry_count,startup->bind_retry_delay,"Telnet Server",lprintf);
-	if(startup->telnet_port < IPPORT_RESERVED) {
-		if(startup->seteuid!=NULL)
-			startup->seteuid(TRUE);
-	}
-	if(result != 0) {
-		lprintf(LOG_CRIT,"%s",BIND_FAILURE_HELP);
-		cleanup(1);
-		return;
-	}
-
-    result = listen(telnet_socket, 1);
+	/*
+	 * Add interfaces
+	 */
+	xpms_add_list(ts_set, PF_UNSPEC, SOCK_STREAM, 0, startup->telnet_interfaces, startup->telnet_port, "Telnet Server", sock_cb, startup->seteuid, &telnet_cb);
 
-	if(result != 0) {
-		lprintf(LOG_CRIT,"!ERROR %d (%d) listening on Telnet socket", result, ERROR_VALUE);
-		cleanup(1);
-		return;
-	}
 	lprintf(LOG_INFO,"Telnet Server listening on port %u",startup->telnet_port);
 
 	if(startup->options&BBS_OPT_ALLOW_RLOGIN) {
-
 		/* open a socket and wait for a client */
-
-		rlogin_socket = open_socket(SOCK_STREAM, "rlogin");
-
-		if(rlogin_socket == INVALID_SOCKET) {
-			lprintf(LOG_CRIT,"!ERROR %d creating RLogin socket", ERROR_VALUE);
-			cleanup(1);
-			return;
-		}
-
-		lprintf(LOG_DEBUG,"RLogin socket %d opened",rlogin_socket);
-
-		/*****************************/
-		/* Listen for incoming calls */
-		/*****************************/
-		memset(&server_addr, 0, sizeof(server_addr));
-
-		server_addr.sin_addr.s_addr = htonl(startup->rlogin_interface);
-		server_addr.sin_family = AF_INET;
-		server_addr.sin_port   = htons(startup->rlogin_port);
-
-		if(startup->rlogin_port < IPPORT_RESERVED) {
-			if(startup->seteuid!=NULL)
-				startup->seteuid(FALSE);
-		}
-		result = retry_bind(rlogin_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
-			,startup->bind_retry_count,startup->bind_retry_delay,"RLogin Server",lprintf);
-		if(startup->rlogin_port < IPPORT_RESERVED) {
-			if(startup->seteuid!=NULL)
-				startup->seteuid(TRUE);
-		}
-		if(result != 0) {
-			lprintf(LOG_CRIT,"%s",BIND_FAILURE_HELP);
-			cleanup(1);
-			return;
-		}
-
-		result = listen(rlogin_socket, 1);
-
-		if(result != 0) {
-			lprintf(LOG_CRIT,"!ERROR %d (%d) listening on RLogin socket", result, ERROR_VALUE);
-			cleanup(1);
-			return;
-		}
+		rlogin_cb.protocol="rlogin";
+		rlogin_cb.startup=startup;
+		xpms_add_list(ts_set, PF_UNSPEC, SOCK_STREAM, 0, startup->rlogin_interfaces, startup->rlogin_port, "RLogin Server", sock_cb, startup->seteuid, &rlogin_cb);
 		lprintf(LOG_INFO,"RLogin Server listening on port %u",startup->rlogin_port);
 	}
 
@@ -4761,15 +4722,10 @@ void DLLCALL bbs_thread(void* arg)
 		/* Get the private key... first try loading it from a file... */
 		SAFEPRINTF2(str,"%s%s",scfg.ctrl_dir,"cryptlib.key");
 		if(cryptStatusOK(cryptKeysetOpen(&ssh_keyset, CRYPT_UNUSED, CRYPT_KEYSET_FILE, str, CRYPT_KEYOPT_NONE))) {
-			if(cryptStatusOK(cryptGetPrivateKey(ssh_keyset, &ssh_context, CRYPT_KEYID_NAME, "ssh_server", scfg.sys_pass)))
-				loaded_key=true;
-			cryptKeysetClose(ssh_keyset);
-			/* Failed to load the key... delete the keyfile and create a new one */
-			if(!loaded_key)
-				remove(str);
+			if(!cryptStatusOK(cryptGetPrivateKey(ssh_keyset, &ssh_context, CRYPT_KEYID_NAME, "ssh_server", scfg.sys_pass)))
+				goto NO_SSH;
 		}
-
-		if(!loaded_key) {
+		else {
 			/* Couldn't do that... create a new context and use the key from there... */
 
 			if(!cryptStatusOK(i=cryptCreateContext(&ssh_context, CRYPT_UNUSED, CRYPT_ALGO_RSA))) {
@@ -4793,56 +4749,16 @@ void DLLCALL bbs_thread(void* arg)
 		}
 
 		/* open a socket and wait for a client */
-
-		ssh_socket = open_socket(SOCK_STREAM, "ssh");
-
-		if(ssh_socket == INVALID_SOCKET) {
-			lprintf(LOG_CRIT,"!ERROR %d creating SSH socket", ERROR_VALUE);
-			cleanup(1);
-			return;
-		}
-
-		lprintf(LOG_DEBUG,"SSH socket %d opened",ssh_socket);
-
-		/*****************************/
-		/* Listen for incoming calls */
-		/*****************************/
-		memset(&server_addr, 0, sizeof(server_addr));
-
-		server_addr.sin_addr.s_addr = htonl(startup->ssh_interface);
-		server_addr.sin_family = AF_INET;
-		server_addr.sin_port   = htons(startup->ssh_port);
-
-		if(startup->ssh_port < IPPORT_RESERVED) {
-			if(startup->seteuid!=NULL)
-				startup->seteuid(FALSE);
-		}
-		result = retry_bind(ssh_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
-			,startup->bind_retry_count,startup->bind_retry_delay,"SSH Server",lprintf);
-		if(startup->ssh_port < IPPORT_RESERVED) {
-			if(startup->seteuid!=NULL)
-				startup->seteuid(TRUE);
-		}
-		if(result != 0) {
-			lprintf(LOG_CRIT,"%s",BIND_FAILURE_HELP);
-			cleanup(1);
-			return;
-		}
-
-		result = listen(ssh_socket, 1);
-
-		if(result != 0) {
-			lprintf(LOG_CRIT,"!ERROR %d (%d) listening on SSH socket", result, ERROR_VALUE);
-			cleanup(1);
-			return;
-		}
+		ssh_cb.protocol="ssh";
+		ssh_cb.startup=startup;
+		xpms_add_list(ts_set, PF_UNSPEC, SOCK_STREAM, 0, startup->ssh_interfaces, startup->ssh_port, "SSH Server", sock_cb, startup->seteuid, &ssh_cb);
 		lprintf(LOG_INFO,"SSH Server listening on port %u",startup->ssh_port);
 	}
 NO_SSH:
 #endif
 
-	sbbs = new sbbs_t(0, server_addr
-		,"Terminal Server", telnet_socket, &scfg, text, NULL);
+	sbbs = new sbbs_t(0, &server_addr, sizeof(server_addr)
+		,"Terminal Server", ts_set->socks[0].sock, &scfg, text, NULL);
 	if(sbbs->init()==false) {
 		lputs(LOG_CRIT,"!BBS initialization failed");
 		cleanup(1);
@@ -4852,7 +4768,7 @@ NO_SSH:
 	_beginthread(output_thread, 0, sbbs);
 
 	if(!(startup->options&BBS_OPT_NO_EVENTS)) {
-		events = new sbbs_t(0, server_addr
+		events = new sbbs_t(0, &server_addr, sizeof(server_addr)
 			,"BBS Events", INVALID_SOCKET, &scfg, text, NULL);
 		if(events->init()==false) {
 			lputs(LOG_CRIT,"!Events initialization failed");
@@ -4922,44 +4838,19 @@ NO_SSH:
 
 #ifdef __unix__	//	unix-domain spy sockets
 	for(i=first_node;i<=last_node && !(startup->options&BBS_OPT_NO_SPY_SOCKETS);i++)  {
-	    if((uspy_listen_socket[i-1]=socket(PF_UNIX,SOCK_STREAM,0))==INVALID_SOCKET)
-	        lprintf(LOG_ERR,"Node %d !ERROR %d creating local spy socket"
-	            , i, errno);
-	    else {
-	        lprintf(LOG_INFO,"Node %d local spy using socket %d", i, uspy_listen_socket[i-1]);
-	        if(startup!=NULL && startup->socket_open!=NULL)
-	            startup->socket_open(startup->cbdata,TRUE);
-	    }
-	
-	    uspy_addr.sun_family=AF_UNIX;
-	    if((unsigned int)snprintf(str,sizeof(uspy_addr.sun_path),
+	    if((unsigned int)snprintf(str,sizeof(uspy_addr.un.sun_path),
 	            "%slocalspy%d.sock", startup->temp_dir, i)
-	            >=sizeof(uspy_addr.sun_path))
-	        uspy_listen_socket[i-1]=INVALID_SOCKET;
+	            >=sizeof(uspy_addr.un.sun_path)) {
+			lprintf(LOG_ERR,"Node %d !ERROR local spy socket path \"%slocalspy%d.sock\" too long."
+				, i, startup->temp_dir, i);
+			continue;
+		}
 	    else  {
-	        strcpy(uspy_addr.sun_path,str);
-	        if(fexist(str))
-	            unlink(str);
-	    }
-	    if(uspy_listen_socket[i-1]!=INVALID_SOCKET) {
-	        uspy_addr_len=SUN_LEN(&uspy_addr);
-	        if(bind(uspy_listen_socket[i-1], (struct sockaddr *) &uspy_addr, uspy_addr_len)) {
-	            lprintf(LOG_ERR,"Node %d !ERROR %d binding local spy socket %d to %s"
-	                , i, errno, uspy_listen_socket[i-1], uspy_addr.sun_path);
-	            close_socket(uspy_listen_socket[i-1]);
-				uspy_listen_socket[i-1]=INVALID_SOCKET;
-	            continue;
-	        }
-            lprintf(LOG_INFO,"Node %d local spy socket %d bound to %s"
-                , i, uspy_listen_socket[i-1], uspy_addr.sun_path);
-	        if(listen(uspy_listen_socket[i-1],1))  {
-	            lprintf(LOG_ERR,"Node %d !ERROR %d listening local spy socket %d"
-					,i, errno, uspy_listen_socket[i-1]);
-	            close_socket(uspy_listen_socket[i-1]);
-				uspy_listen_socket[i-1]=INVALID_SOCKET;
-	            continue;
-			}
-	        uspy_addr_len=sizeof(uspy_addr);
+			if(xpms_add(ts_set, PF_UNIX, SOCK_STREAM, 0, str, 0, "Spy Socket", sock_cb, NULL, &uspy_cb[i-1]))
+				lprintf(LOG_INFO,"Node %d local spy using socket %s", i, str);
+			else
+				lprintf(LOG_ERR,"Node %d !ERROR %d creating local spy socket %s"
+					, i, errno, str);
 	    }
 	}
 #endif // __unix__ (unix-domain spy sockets)
@@ -4992,22 +4883,21 @@ NO_SSH:
 					break;
 
 				if((p=semfile_list_check(&initialized,recycle_semfiles))!=NULL) {
-					lprintf(LOG_INFO,"%04d Recycle semaphore file (%s) detected"
-						,telnet_socket,p);
+					lprintf(LOG_INFO,"Recycle semaphore file (%s) detected"
+						,p);
 					break;
 				}
 				if(startup->recycle_now==TRUE) {
-					lprintf(LOG_INFO,"%04d Recycle semaphore signaled",telnet_socket);
+					lprintf(LOG_INFO,"Recycle semaphore signaled");
 					startup->recycle_now=FALSE;
 					break;
 				}
 			}
 			if(((p=semfile_list_check(&initialized,shutdown_semfiles))!=NULL
-					&& lprintf(LOG_INFO,"%04d Shutdown semaphore file (%s) detected"
-						,telnet_socket,p))
+					&& lprintf(LOG_INFO,"Shutdown semaphore file (%s) detected"
+						,p))
 				|| (startup->shutdown_now==TRUE
-					&& lprintf(LOG_INFO,"%04d Shutdown semaphore signaled"
-						,telnet_socket))) {
+					&& lprintf(LOG_INFO,"Shutdown semaphore signaled"))) {
 				startup->shutdown_now=FALSE;
 				terminate_server=TRUE;
 				break;
@@ -5021,62 +4911,15 @@ NO_SSH:
 #endif
 
 		/* now wait for connection */
-
-		FD_ZERO(&socket_set);
-		high_socket_set=0;
-		if(telnet_socket!=INVALID_SOCKET) {
-			FD_SET(telnet_socket,&socket_set);
-			high_socket_set=telnet_socket+1;
-		}
-		if(startup->options&BBS_OPT_ALLOW_RLOGIN 
-			&& rlogin_socket!=INVALID_SOCKET) {
-			FD_SET(rlogin_socket,&socket_set);
-			if(rlogin_socket+1>high_socket_set)
-				high_socket_set=rlogin_socket+1;
-		}
-#ifdef USE_CRYPTLIB
-		if(startup->options&BBS_OPT_ALLOW_SSH
-			&& ssh_socket!=INVALID_SOCKET) {
-			FD_SET(ssh_socket,&socket_set);
-			if(ssh_socket+1>high_socket_set)
-				high_socket_set=ssh_socket+1;
-		}
-#endif
-#ifdef __unix__
-		for(i=first_node;i<=last_node;i++)  {
-			if(uspy_listen_socket[i-1]!=INVALID_SOCKET)  {
-				FD_SET(uspy_listen_socket[i-1],&socket_set);
-				if(uspy_listen_socket[i-1]+1>high_socket_set)
-					high_socket_set=uspy_listen_socket[i-1]+1;
-			}
-			if(uspy_socket[i-1]!=INVALID_SOCKET)  {
-				FD_SET(uspy_socket[i-1],&socket_set);
-				if(uspy_socket[i-1]+1>high_socket_set)
-					high_socket_set=uspy_listen_socket[i-1]+1;
-			}
-		}
-#endif
-
-		struct timeval tv;
-		tv.tv_sec=startup->sem_chk_freq;
-		tv.tv_usec=0;
-
-		if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) {
-			if(i==0)
-				continue;
-			if(ERROR_VALUE==EINTR)
-				lprintf(LOG_DEBUG,"Terminal Server listening interrupted");
-			else if(ERROR_VALUE == ENOTSOCK)
-            	lprintf(LOG_NOTICE,"Terminal Server sockets closed");
-			else
-				lprintf(LOG_WARNING,"!ERROR %d selecting sockets",ERROR_VALUE);
-			continue;
-		}
+		client_addr_len = sizeof(client_addr);
+		client_socket=xpms_accept(ts_set, &client_addr
+	        	,&client_addr_len, startup->sem_chk_freq*1000, &ts_cb);
 
 		if(terminate_server)	/* terminated */
 			break;
 
-		client_addr_len = sizeof(client_addr);
+		if(client_socket == INVALID_SOCKET)
+			continue;
 
 		bool rlogin = false;
 #ifdef USE_CRYPTLIB
@@ -5084,23 +4927,13 @@ NO_SSH:
 #endif
 
 		is_client=FALSE;
-		if(telnet_socket!=INVALID_SOCKET 
-			&& FD_ISSET(telnet_socket,&socket_set)) {
-			client_socket = accept_socket(telnet_socket, (struct sockaddr *)&client_addr
-	        	,&client_addr_len);
+		if(ts_cb == &telnet_cb) {
 	        is_client=TRUE;
-		} else if(rlogin_socket!=INVALID_SOCKET 
-			&& FD_ISSET(rlogin_socket,&socket_set)) {
-			client_socket = accept_socket(rlogin_socket, (struct sockaddr *)&client_addr
-	        	,&client_addr_len);
+		} else if(ts_cb == &rlogin_cb) {
 			rlogin = true;
 			is_client=TRUE;
 #ifdef USE_CRYPTLIB
-		} else if(ssh_socket!=INVALID_SOCKET 
-			&& FD_ISSET(ssh_socket,&socket_set)) {
-
-			client_socket = accept_socket(ssh_socket, (struct sockaddr *)&client_addr
-	        	,&client_addr_len);
+		} else if(ts_cb == &ssh_cb) {
 			ssh = true;
 			is_client=TRUE;
 			sbbs->ssh_mode=true;
@@ -5108,8 +4941,7 @@ NO_SSH:
 		} else {
 #ifdef __unix__
 			for(i=first_node;i<=last_node;i++)  {
-				if(uspy_socket[i-1]!=INVALID_SOCKET
-				&& FD_ISSET(uspy_socket[i-1],&socket_set)) {
+				if(&uspy_cb[i-1] == ts_cb) {
 					if(node_socket[i-1]==INVALID_SOCKET)
 						read(uspy_socket[i-1],str,sizeof(str));
 					if(!socket_check(uspy_socket[i-1],NULL,NULL,0)) {
@@ -5118,25 +4950,17 @@ NO_SSH:
 						uspy_socket[i-1]=INVALID_SOCKET;
 					}
 				}
-				if(uspy_listen_socket[i-1]!=INVALID_SOCKET
-				&& FD_ISSET(uspy_listen_socket[i-1],&socket_set)) {
+				if(&uspy_cb[i-1] == ts_cb) {
 					BOOL already_connected=(uspy_socket[i-1]!=INVALID_SOCKET);
-					SOCKET new_socket=INVALID_SOCKET;
-					new_socket = accept(uspy_listen_socket[i-1], (struct sockaddr *)&uspy_addr
-						,&uspy_addr_len);
-					if(new_socket < 0)  {
-						lprintf(LOG_ERR,"!ERROR Spy socket for node %d unable to accept()",i);
-						close_socket(uspy_listen_socket[i-1]);
-						uspy_listen_socket[i-1]=INVALID_SOCKET;
-					}
+					SOCKET new_socket=client_socket;
 					fcntl(new_socket,F_SETFL,fcntl(new_socket,F_GETFL)|O_NONBLOCK);
 					if(already_connected)  {
-						lprintf(LOG_ERR,"!ERROR Spy socket %s already in use",uspy_addr.sun_path);
+						lprintf(LOG_ERR,"!ERROR Spy socket %s already in use",uspy_addr.un.sun_path);
 						send(new_socket,"Spy socket already in use.\r\n",27,0);
 						close_socket(new_socket);
 					}
 					else  {
-						lprintf(LOG_ERR,"!Spy socket %s (%d) connected",uspy_addr.sun_path,new_socket);
+						lprintf(LOG_ERR,"!Spy socket %s (%d) connected",uspy_addr.un.sun_path,new_socket);
 						uspy_socket[i-1]=new_socket;
 						SAFEPRINTF(str,"Spy connection established to node %d\r\n",i);
 						send(uspy_socket[i-1],str,strlen(str),0);
@@ -5162,15 +4986,15 @@ NO_SSH:
 #endif
 			lprintf(LOG_ERR,"!ERROR %d accepting connection", ERROR_VALUE);
 #ifdef _WIN32
-			if(WSAGetLastError()==WSAENOBUFS)	/* recycle (re-init WinSock) on this error */
+			if(WSAGetLastError()==WSAENOBUFS) {	/* recycle (re-init WinSock) on this error */
 				break;
+			}
 #endif
-			SSH_END();
 			continue;
 		}
-		char host_ip[32];
+		char host_ip[INET6_ADDRSTRLEN];
 
-		strcpy(host_ip,inet_ntoa(client_addr.sin_addr));
+		inet_addrtop(&client_addr, host_ip, sizeof(host_ip));
 
 		if(trashcan(&scfg,host_ip,"ip-silent")) {
 			SSH_END();
@@ -5185,7 +5009,7 @@ NO_SSH:
 #else
 			,rlogin ? "RLogin" : "Telnet"
 #endif
-			, host_ip, ntohs(client_addr.sin_port));
+			, host_ip, inet_addrport(&client_addr));
 
 #ifdef _WIN32
 		if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)) 
@@ -5284,19 +5108,14 @@ NO_SSH:
 
 		sbbs->bprintf("Connection from: %s\r\n", host_ip);
 
-		struct hostent* h;
-		if(startup->options&BBS_OPT_NO_HOST_LOOKUP)
-			h=NULL;
-		else {
+		if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP)) {
 			sbbs->bprintf("Resolving hostname...");
-			h=gethostbyaddr((char *)&client_addr.sin_addr
-				,sizeof(client_addr.sin_addr),AF_INET);
+			if(getnameinfo(&client_addr.addr, client_addr_len, host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD))
+				strcpy(host_name, "<no name>");
 			sbbs->putcom(crlf);
 		}
-		if(h!=NULL && h->h_name!=NULL)
-			host_name=h->h_name;
 		else
-			host_name="<no name>";
+			strcpy(host_name, "<no name>");
 
 		if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP))
 			lprintf(LOG_INFO,"%04d Hostname: %s", client_socket, host_name);
@@ -5315,7 +5134,7 @@ NO_SSH:
 		if(startup->options&BBS_OPT_GET_IDENT) {
 			sbbs->bprintf("Resolving identity...");
 			/* ToDo: Make ident timeout configurable */
-			if(identify(&client_addr, startup->telnet_port, str, sizeof(str)-1, /* timeout: */1)) {
+			if(identify(&client_addr, inet_addrport(&client_addr), str, sizeof(str)-1, /* timeout: */1)) {
 				lprintf(LOG_DEBUG,"%04d Ident Response: %s",client_socket, str);
 				identity=strrchr(str,':');
 				if(identity!=NULL) {
@@ -5332,7 +5151,7 @@ NO_SSH:
 		client.time=time32(NULL);
 		SAFECOPY(client.addr,host_ip);
 		SAFECOPY(client.host,host_name);
-		client.port=ntohs(client_addr.sin_port);
+		client.port=inet_addrport(&client_addr);
 #ifdef USE_CRYPTLIB
 		client.protocol=rlogin ? "RLogin":(ssh ? "SSH" : "Telnet");
 #else
@@ -5382,7 +5201,7 @@ NO_SSH:
 
         node_socket[i-1]=client_socket;
 
-		sbbs_t* new_node = new sbbs_t(i, client_addr, host_name
+		sbbs_t* new_node = new sbbs_t(i, &client_addr, client_addr_len, host_name
         	,client_socket
 			,&scfg, text, &client);
 
@@ -5426,6 +5245,7 @@ NO_SSH:
 		}
 #ifdef USE_CRYPTLIB
 		if(ssh) {
+			/* TODO: IPv6? */
 			SOCKET	tmp_sock;
 			SOCKADDR_IN		tmp_addr={0};
 			socklen_t		tmp_addr_len;
@@ -5545,7 +5365,7 @@ NO_PASSTHRU:
 		if(uspy_listen_socket[i]!=INVALID_SOCKET) {
 			close_socket(uspy_listen_socket[i]);
 			uspy_listen_socket[i]=INVALID_SOCKET;
-			snprintf(str,sizeof(uspy_addr.sun_path),"%slocalspy%d.sock", startup->temp_dir, i+1);
+			snprintf(str,sizeof(uspy_addr.un.sun_path),"%slocalspy%d.sock", startup->temp_dir, i+1);
 			if(fexist(str))
 				unlink(str);
 		}
diff --git a/src/sbbs3/newuser.cpp b/src/sbbs3/newuser.cpp
index c08b5c156004bdfbd919012378fa1217e6098d37..3eed0cd80fc53f5d384590a1919d5d84016b964b 100644
--- a/src/sbbs3/newuser.cpp
+++ b/src/sbbs3/newuser.cpp
@@ -101,8 +101,8 @@ BOOL sbbs_t::newuser()
 	useron.sex=' ';
 	useron.prot=cfg.new_prot;
 	SAFECOPY(useron.comp,client_name);	/* hostname or CID name */
-	SAFECOPY(useron.note,cid);			/* IP address or CID number */
-	if((i=userdatdupe(0,U_NOTE,LEN_NOTE,cid, /* del */true))!=0) {	/* Duplicate IP address */
+	SAFECOPY(useron.ipaddr,cid);			/* IP address or CID number */
+	if((i=userdatdupe(0,U_IPADDR,LEN_IPADDR,cid, /* del */true))!=0) {	/* Duplicate IP address */
 		SAFEPRINTF2(useron.comment,"Warning: same IP address as user #%d %s"
 			,i,username(&cfg,i,str));
 		logline(LOG_NOTICE,"N!",useron.comment); 
diff --git a/src/sbbs3/objects.mk b/src/sbbs3/objects.mk
index d40bdb2eea56dad46aff99308503748c932f2765..880a0ef222d5bc2ae0708d13d19703d30b586c96 100644
--- a/src/sbbs3/objects.mk
+++ b/src/sbbs3/objects.mk
@@ -114,7 +114,7 @@ OBJS	=	$(MTOBJODIR)$(DIRSEP)ansiterm$(OFILE) \
 			$(MTOBJODIR)$(DIRSEP)xtrn$(OFILE)\
 			$(MTOBJODIR)$(DIRSEP)xtrn_sec$(OFILE)\
 			$(MTOBJODIR)$(DIRSEP)yenc$(OFILE)\
-			$(MTOBJODIR)$(DIRSEP)ver$(OFILE)
+			$(MTOBJODIR)$(DIRSEP)ver$(OFILE)\
 
 # Must add new additions to MONO_OBJS too!
 CON_OBJS	= $(MTOBJODIR)$(DIRSEP)sbbscon$(OFILE) \
@@ -133,6 +133,7 @@ MAIL_OBJS	= $(MTOBJODIR)$(DIRSEP)mailsrvr$(OFILE) \
 
 # Must add new additions to MONO_OBJS too!
 WEB_OBJS	= $(MTOBJODIR)$(DIRSEP)websrvr$(OFILE) \
+			$(MTOBJODIR)$(DIRSEP)ssl$(OFILE) \
 			$(MTOBJODIR)$(DIRSEP)base64$(OFILE) \
 			$(MTOBJODIR)$(DIRSEP)ars$(OFILE) \
 			$(MTOBJODIR)$(DIRSEP)ringbuf$(OFILE)
@@ -150,7 +151,8 @@ MONO_OBJS	= \
 			$(MTOBJODIR)$(DIRSEP)sbbs_ini$(OFILE) \
 			$(MTOBJODIR)$(DIRSEP)sbbscon$(OFILE) \
 			$(MTOBJODIR)$(DIRSEP)services$(OFILE) \
-			$(MTOBJODIR)$(DIRSEP)websrvr$(OFILE)
+			$(MTOBJODIR)$(DIRSEP)websrvr$(OFILE) \
+			$(MTOBJODIR)$(DIRSEP)ssl$(OFILE)
 
 BAJA_OBJS = \
 			$(OBJODIR)$(DIRSEP)baja$(OFILE) \
diff --git a/src/sbbs3/putnode.cpp b/src/sbbs3/putnode.cpp
index 2343ed0b4032fdd453797ef9873e423ab5ab7c0b..13c2af4ea5b44d87aa9c5e3ac5a523c37328fff7 100644
--- a/src/sbbs3/putnode.cpp
+++ b/src/sbbs3/putnode.cpp
@@ -69,7 +69,7 @@ int sbbs_t::putnodedat(uint number, node_t* node)
 				,getage(&cfg,useron.birth)
 				,useron.sex
 				,useron.comp
-				,useron.note
+				,useron.ipaddr
 				,unixtodstr(&cfg,useron.firston,firston)
 				,node->aux&0xff
 				,node->connection
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index 65e7b320b0b71f3c60ba0c841c935403c6658f7a..c21c690ec0af02d21bcb060f34f6f25a8a2d49b7 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -264,6 +264,7 @@ extern int	thread_suid_broken;			/* NPTL is no longer broken */
 #include "filewrap.h"
 #include "datewrap.h"
 #include "sockwrap.h"
+#include "multisock.h"
 #include "eventwrap.h"
 #include "link_list.h"
 #include "msg_queue.h"
@@ -299,7 +300,7 @@ class sbbs_t
 
 public:
 
-	sbbs_t(ushort node_num, SOCKADDR_IN addr, const char* host_name, SOCKET
+	sbbs_t(ushort node_num, union xp_sockaddr *addr, size_t addr_len, const char* host_name, SOCKET
 		,scfg_t*, char* text[], client_t* client_info);
 	~sbbs_t();
 
@@ -311,10 +312,11 @@ public:
 	client_t client;
 	SOCKET	client_socket;
 	SOCKET	client_socket_dup;
-	SOCKADDR_IN	client_addr;
+	union xp_sockaddr	client_addr;
 	char	client_name[128];
 	char	client_ident[128];
-	DWORD	local_addr;
+	char	client_ipaddr[INET6_ADDRSTRLEN];
+	char	local_addr[INET6_ADDRSTRLEN];
 #ifdef USE_CRYPTLIB
 	CRYPT_SESSION	ssh_session;
 	bool	ssh_mode;
@@ -1087,7 +1089,7 @@ extern "C" {
 	DLLEXPORT int		DLLCALL errorlog(scfg_t* cfg, const char* host, const char* text);
 
 	DLLEXPORT BOOL		DLLCALL hacklog(scfg_t* cfg, char* prot, char* user, char* text
-										,char* host, SOCKADDR_IN* addr);
+										,char* host, union xp_sockaddr* addr);
 	DLLEXPORT BOOL		DLLCALL spamlog(scfg_t* cfg, char* prot, char* action, char* reason
 										,char* host, char* ip_addr, char* to, char* from);
 
@@ -1111,7 +1113,7 @@ extern "C" {
 	typedef struct {
 		const char*		name;
 		JSNative        call;
-		uint8           nargs;
+		uint8_t         nargs;
 		int				type;		/* return type */
 		const char*		args;		/* arguments */
 		const char*		desc;		/* description */
@@ -1120,8 +1122,8 @@ extern "C" {
 
 	typedef struct {
 		const char      *name;
-		int8            tinyid;
-		uint8           flags;
+		int8_t          tinyid;
+		uint8_t         flags;
 		int				ver;		/* version added/modified */
 	} jsSyncPropertySpec;
 
@@ -1156,12 +1158,12 @@ extern "C" {
 	/* main.cpp */
 	DLLEXPORT JSBool	DLLCALL js_DescribeSyncObject(JSContext* cx, JSObject* obj, const char*, int ver);
 	DLLEXPORT JSBool	DLLCALL js_DescribeSyncConstructor(JSContext* cx, JSObject* obj, const char*);
-	DLLEXPORT JSBool	DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec*, BOOL append);
+	DLLEXPORT JSBool	DLLCALL js_DefineSyncMethods(JSContext* cx, JSObject* obj, jsSyncMethodSpec*);
 	DLLEXPORT JSBool	DLLCALL js_DefineSyncProperties(JSContext* cx, JSObject* obj, jsSyncPropertySpec*);
 	DLLEXPORT JSBool	DLLCALL js_SyncResolve(JSContext* cx, JSObject* obj, char *name, jsSyncPropertySpec* props, jsSyncMethodSpec* funcs, jsConstIntSpec* consts, int flags);
 	DLLEXPORT JSBool	DLLCALL js_DefineConstIntegers(JSContext* cx, JSObject* obj, jsConstIntSpec*, int flags);
 	DLLEXPORT JSBool	DLLCALL js_CreateArrayOfStrings(JSContext* cx, JSObject* parent
-														,const char* name, char* str[], uintN flags);
+														,const char* name, char* str[], unsigned flags);
 
 	/* js_server.c */
 	DLLEXPORT JSObject* DLLCALL js_CreateServerObject(JSContext* cx, JSObject* parent
@@ -1176,7 +1178,7 @@ extern "C" {
 		sem_t				bg_sem;
 		str_list_t			exit_func;
 	} global_private_t;
-	DLLEXPORT BOOL DLLCALL js_argc(JSContext *cx, uintN argc, uintN min);
+	DLLEXPORT BOOL DLLCALL js_argc(JSContext *cx, unsigned argc, unsigned min);
 	DLLEXPORT BOOL DLLCALL js_CreateGlobalObject(JSContext* cx, scfg_t* cfg, jsSyncMethodSpec* methods, js_startup_t*, JSObject**);
 	DLLEXPORT BOOL	DLLCALL js_CreateCommonObjects(JSContext* cx
 													,scfg_t* cfg				/* common */
@@ -1238,8 +1240,13 @@ extern "C" {
 	DLLEXPORT JSObject* DLLCALL js_CreateSocketClass(JSContext* cx, JSObject* parent);
 	DLLEXPORT JSObject* DLLCALL js_CreateSocketObject(JSContext* cx, JSObject* parent
 													,char *name, SOCKET sock);
+	DLLEXPORT JSObject* DLLCALL js_CreateSocketObjectFromSet(JSContext* cx, JSObject* parent
+													,char *name, struct xpms_set *set);
+
 	DLLEXPORT void		DLLCALL js_timeval(JSContext* cx, jsval val, struct timeval* tv);
 	DLLEXPORT SOCKET	DLLCALL js_socket(JSContext *cx, jsval val);
+    DLLEXPORT SOCKET	DLLCALL js_socket_add(JSContext *cx, jsval val, fd_set *fds);
+	DLLEXPORT BOOL		DLLCALL js_socket_isset(JSContext *cx, jsval val, fd_set *fds);
 
 	/* js_queue.c */
 	DLLEXPORT JSObject* DLLCALL js_CreateQueueClass(JSContext* cx, JSObject* parent);
@@ -1251,7 +1258,7 @@ extern "C" {
 	DLLEXPORT JSObject* DLLCALL js_CreateFileClass(JSContext* cx, JSObject* parent);
 
 	/* js_sprintf.c */
-	DLLEXPORT char*		DLLCALL js_sprintf(JSContext* cx, uint argn, uintN argc, jsval *argv);
+	DLLEXPORT char*		DLLCALL js_sprintf(JSContext* cx, uint argn, unsigned argc, jsval *argv);
 	DLLEXPORT void		DLLCALL js_sprintf_free(char *);
 
 	/* js_console.cpp */
@@ -1294,7 +1301,7 @@ BOOL 	md(char *path);
 	int 	lprintf(int level, const char *fmt, ...);	/* log output */
 	int 	eprintf(int level, const char *fmt, ...);	/* event log */
 	SOCKET	open_socket(int type, const char* protocol);
-	SOCKET	accept_socket(SOCKET s, SOCKADDR* addr, socklen_t* addrlen);
+	SOCKET	accept_socket(SOCKET s, union xp_sockaddr* addr, socklen_t* addrlen);
 	int		close_socket(SOCKET);
 	u_long	resolve_ip(char *addr);
 
diff --git a/src/sbbs3/sbbs_ini.c b/src/sbbs3/sbbs_ini.c
index d8849c36989d458bdde8563bf794b68dcfbcb1c9..3b58cf9f87abed59508e4adc39af9a5097d292b9 100644
--- a/src/sbbs3/sbbs_ini.c
+++ b/src/sbbs3/sbbs_ini.c
@@ -48,7 +48,9 @@ static const char*  strAutoStart="AutoStart";
 static const char*  strCtrlDirectory="CtrlDirectory";
 static const char*  strTempDirectory="TempDirectory";
 static const char*	strOptions="Options";
-static const char*	strInterface="Interface";
+static const char*	strOutgoing4="OutgoingV4";
+static const char*	strOutgoing6="OutgoingV6";
+static const char*	strInterfaces="Interface";
 static const char*	strPort="Port";
 static const char*	strMaxClients="MaxClients";
 static const char*	strMaxInactivity="MaxInactivity";
@@ -193,6 +195,7 @@ 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) {
@@ -211,7 +214,9 @@ static void get_ini_globals(str_list_t list, global_startup_t* global)
         SAFECOPY(global->host_name,value);
 
 	global->sem_chk_freq=iniGetShortInt(list,section,strSemFileCheckFrequency,0);
-	global->interface_addr=iniGetIpAddress(list,section,strInterface,INADDR_ANY);
+	global->interfaces=iniGetStringList(list,section,strInterfaces, ",", "0.0.0.0,::");
+	global->outgoing4.s_addr=iniGetIpAddress(list,section,strOutgoing4,0);
+	global->outgoing6=iniGetIp6Address(list,section,strOutgoing6,wildcard6);
 	global->log_level=iniGetLogLevel(list,section,strLogLevel,DEFAULT_LOG_LEVEL);
 	global->bind_retry_count=iniGetInteger(list,section,strBindRetryCount,DEFAULT_BIND_RETRY_COUNT);
 	global->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,DEFAULT_BIND_RETRY_DELAY);
@@ -254,6 +259,8 @@ void sbbs_read_ini(
 	char		value[INI_MAX_VALUE_LEN];
 	str_list_t	list;
 	global_startup_t global_buf;
+	struct in6_addr	wildcard6 = {0};
+	char		*global_interfaces;
 
 	if(global==NULL) {
 		memset(&global_buf,0,sizeof(global_buf));
@@ -271,7 +278,9 @@ void sbbs_read_ini(
 		if(mail!=NULL)		SAFECOPY(mail->ctrl_dir,global->ctrl_dir);
 		if(services!=NULL)	SAFECOPY(services->ctrl_dir,global->ctrl_dir);
 	}
-													
+
+	global_interfaces = strListCombine(global->interfaces, NULL, 16384, ",");
+
 	/***********************************************************************/
 	section = "BBS";
 
@@ -280,20 +289,25 @@ void sbbs_read_ini(
 
 	if(bbs!=NULL) {
 
-		bbs->telnet_interface
-			=iniGetIpAddress(list,section,"TelnetInterface",global->interface_addr);
+		bbs->outgoing4.s_addr
+			=iniGetIpAddress(list,section,strOutgoing4,global->outgoing4.s_addr);
+		bbs->outgoing6
+			=iniGetIp6Address(list,section,strOutgoing6,global->outgoing6);
+
 		bbs->telnet_port
 			=iniGetShortInt(list,section,"TelnetPort",IPPORT_TELNET);
+		bbs->telnet_interfaces
+			=iniGetStringList(list,section,"TelnetInterface",",",global_interfaces);
 
-		bbs->rlogin_interface
-			=iniGetIpAddress(list,section,"RLoginInterface",global->interface_addr);
 		bbs->rlogin_port
 			=iniGetShortInt(list,section,"RLoginPort",513);
+		bbs->rlogin_interfaces
+			=iniGetStringList(list,section,"RLoginInterface",",",global_interfaces);
 
-		bbs->ssh_interface
-			=iniGetIpAddress(list,section,"SSHInterface",global->interface_addr);
 		bbs->ssh_port
 			=iniGetShortInt(list,section,"SSHPort",22);
+		bbs->ssh_interfaces
+			=iniGetStringList(list,section,"SSHInterface",",",global_interfaces);
 
 		bbs->first_node
 			=iniGetShortInt(list,section,"FirstNode",1);
@@ -371,10 +385,14 @@ void sbbs_read_ini(
 
 	if(ftp!=NULL) {
 
-		ftp->interface_addr
-			=iniGetIpAddress(list,section,strInterface,global->interface_addr);
+		ftp->outgoing4.s_addr
+			=iniGetIpAddress(list,section,strOutgoing4,global->outgoing4.s_addr);
+		ftp->outgoing6
+			=iniGetIp6Address(list,section,strOutgoing6,global->outgoing6);
 		ftp->port
 			=iniGetShortInt(list,section,strPort,IPPORT_FTP);
+		ftp->interfaces
+			=iniGetStringList(list,section,strInterfaces,",",global_interfaces);
 		ftp->max_clients
 			=iniGetShortInt(list,section,strMaxClients,FTP_DEFAULT_MAX_CLIENTS);
 		ftp->max_inactivity
@@ -389,8 +407,10 @@ void sbbs_read_ini(
 			=iniGetBytes(list,section,"MaxFileSize",1,0);
 
 		/* Passive transfer settings (for stupid firewalls/NATs) */
-		ftp->pasv_ip_addr
+		ftp->pasv_ip_addr.s_addr
 			=iniGetIpAddress(list,section,"PasvIpAddress",0);
+		ftp->pasv_ip6_addr
+			=iniGetIp6Address(list,section,"PasvIp6Address",wildcard6);
 		ftp->pasv_port_low
 			=iniGetShortInt(list,section,"PasvPortLow",IPPORT_RESERVED);
 		ftp->pasv_port_high
@@ -442,12 +462,18 @@ void sbbs_read_ini(
 
 	if(mail!=NULL) {
 
-		mail->interface_addr
-			=iniGetIpAddress(list,section,strInterface,global->interface_addr);
+		mail->interfaces
+			=iniGetStringList(list,section,"SMTPInterface",",",global_interfaces);
+		mail->outgoing4.s_addr
+			=iniGetIpAddress(list,section,strOutgoing4,global->outgoing4.s_addr);
+		mail->outgoing6
+			=iniGetIp6Address(list,section,strOutgoing6,global->outgoing6);
 		mail->smtp_port
 			=iniGetShortInt(list,section,"SMTPPort",IPPORT_SMTP);
 		mail->submission_port
 			=iniGetShortInt(list,section,"SubmissionPort",IPPORT_SUBMISSION);
+		mail->pop3_interfaces
+			=iniGetStringList(list,section,"POP3Interface",",",global_interfaces);
 		mail->pop3_port
 			=iniGetShortInt(list,section,"POP3Port",IPPORT_POP3);
 		mail->relay_port
@@ -532,8 +558,12 @@ void sbbs_read_ini(
 
 	if(services!=NULL) {
 
-		services->interface_addr
-			=iniGetIpAddress(list,section,strInterface,global->interface_addr);
+		services->interfaces
+			=iniGetStringList(list,section,strInterfaces,",",global_interfaces);
+		services->outgoing4.s_addr
+			=iniGetIpAddress(list,section,strOutgoing4,global->outgoing4.s_addr);
+		services->outgoing6
+			=iniGetIp6Address(list,section,strOutgoing6,global->outgoing6);
 
 		services->sem_chk_freq
 			=iniGetShortInt(list,section,strSemFileCheckFrequency,global->sem_chk_freq);
@@ -574,10 +604,18 @@ void sbbs_read_ini(
 
 	if(web!=NULL) {
 
-		web->interface_addr
-			=iniGetIpAddress(list,section,strInterface,global->interface_addr);
+		web->interfaces
+			=iniGetStringList(list,section,strInterfaces,",",global_interfaces);
+		web->tls_interfaces
+			=iniGetStringList(list,section,"TLSInterface",",",global_interfaces);
+		web->outgoing4.s_addr
+			=iniGetIpAddress(list,section,strOutgoing4,global->outgoing4.s_addr);
+		web->outgoing6
+			=iniGetIp6Address(list,section,strOutgoing6,global->outgoing6);
 		web->port
 			=iniGetShortInt(list,section,strPort,IPPORT_HTTP);
+		web->tls_port
+			=iniGetShortInt(list,section,"TLSPort",IPPORT_HTTPS);
 		web->max_clients
 			=iniGetShortInt(list,section,strMaxClients,WEB_DEFAULT_MAX_CLIENTS);
 		web->max_inactivity
@@ -653,6 +691,7 @@ void sbbs_read_ini(
 		web->login_attempt_filter_threshold=iniGetInteger(list,section,strLoginAttemptFilterThreshold,global->login_attempt_filter_threshold);
 	}
 
+	free(global_interfaces);
 	iniFreeStringList(list);
 }
 
@@ -703,7 +742,9 @@ BOOL sbbs_write_ini(
 		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,strInterface,global->interface_addr,&style);
+		iniSetIpAddress(lp,section,strOutgoing4,global->outgoing4.s_addr,&style);
+		iniSetIp6Address(lp,section,strOutgoing6,global->outgoing6,&style);
+		iniSetStringList(lp, section, strInterfaces, ",", global->interfaces, &style);
 		iniSetLogLevel(lp,section,strLogLevel,global->log_level,&style);
 		iniSetInteger(lp,section,strBindRetryCount,global->bind_retry_count,&style);
 		iniSetInteger(lp,section,strBindRetryDelay,global->bind_retry_delay,&style);
@@ -725,24 +766,24 @@ BOOL sbbs_write_ini(
 		if(!iniSetBool(lp,section,strAutoStart,run_bbs,&style))
 			break;
 
-		if(bbs->telnet_interface==global->interface_addr)
+		if(strListCmp(bbs->telnet_interfaces, global->interfaces)==0)
 			iniRemoveValue(lp,section,"TelnetInterface");
-		else if(!iniSetIpAddress(lp,section,"TelnetInterface",bbs->telnet_interface,&style))
+		else if(!iniSetStringList(lp,section,"TelnetInterface", ",", bbs->telnet_interfaces, &style))
 			break;
 
 		if(!iniSetShortInt(lp,section,"TelnetPort",bbs->telnet_port,&style))
 			break;
 
-		if(bbs->rlogin_interface==global->interface_addr)
+		if(strListCmp(bbs->rlogin_interfaces, global->interfaces)==0)
 			iniRemoveValue(lp,section,"RLoginInterface");
-		else if(!iniSetIpAddress(lp,section,"RLoginInterface",bbs->rlogin_interface,&style))
+		else if(!iniSetStringList(lp,section,"RLoginInterface", ",", bbs->rlogin_interfaces,&style))
 			break;
 		if(!iniSetShortInt(lp,section,"RLoginPort",bbs->rlogin_port,&style))
 			break;
 
-		if(bbs->ssh_interface==global->interface_addr)
+		if(strListCmp(bbs->ssh_interfaces, global->interfaces)==0)
 			iniRemoveValue(lp,section,"SSHInterface");
-		else if(!iniSetIpAddress(lp,section,"SSHInterface",bbs->ssh_interface,&style))
+		else if(!iniSetStringList(lp,section,"SSHInterface", ",", bbs->ssh_interfaces,&style))
 			break;
 		if(!iniSetShortInt(lp,section,"SSHPort",bbs->ssh_port,&style))
 			break;
@@ -813,9 +854,19 @@ BOOL sbbs_write_ini(
 		if(!iniSetBool(lp,section,strAutoStart,run_ftp,&style))
 			break;
 
-		if(ftp->interface_addr==global->interface_addr)
-			iniRemoveValue(lp,section,strInterface);
-		else if(!iniSetIpAddress(lp,section,strInterface,ftp->interface_addr,&style))
+		if(strListCmp(ftp->interfaces, global->interfaces)==0)
+			iniRemoveValue(lp,section,strInterfaces);
+		else if(!iniSetStringList(lp,section,strInterfaces,",",ftp->interfaces,&style))
+			break;
+
+		if(ftp->outgoing4.s_addr == global->outgoing4.s_addr)
+			iniRemoveValue(lp,section,strOutgoing4);
+		else if(!iniSetIpAddress(lp, section, strOutgoing4, ftp->outgoing4.s_addr, &style))
+			break;
+
+		if(memcmp(&ftp->outgoing6, &global->outgoing6, sizeof(ftp->outgoing6)))
+			iniRemoveValue(lp,section,strOutgoing6);
+		else if(!iniSetIp6Address(lp, section, strOutgoing6, ftp->outgoing6, &style))
 			break;
 
 		if(!iniSetShortInt(lp,section,strPort,ftp->port,&style))
@@ -832,7 +883,9 @@ BOOL sbbs_write_ini(
 			break;
 
 		/* Passive transfer settings */
-		if(!iniSetIpAddress(lp,section,"PasvIpAddress",ftp->pasv_ip_addr,&style))
+		if(!iniSetIpAddress(lp,section,"PasvIpAddress",ftp->pasv_ip_addr.s_addr,&style))
+			break;
+		if(!iniSetIp6Address(lp,section,"PasvIp6Address",ftp->pasv_ip6_addr,&style))
 			break;
 		if(!iniSetShortInt(lp,section,"PasvPortLow",ftp->pasv_port_low,&style))
 			break;
@@ -899,9 +952,19 @@ BOOL sbbs_write_ini(
 		if(!iniSetBool(lp,section,strAutoStart,run_mail,&style))
 			break;
 
-		if(mail->interface_addr==global->interface_addr)
-			iniRemoveValue(lp,section,strInterface);
-		else if(!iniSetIpAddress(lp,section,strInterface,mail->interface_addr,&style))
+		if(strListCmp(mail->interfaces, global->interfaces)==0)
+			iniRemoveValue(lp,section,strInterfaces);
+		else if(!iniSetStringList(lp,section,strInterfaces,",",mail->interfaces,&style))
+			break;
+
+		if(mail->outgoing4.s_addr == global->outgoing4.s_addr)
+			iniRemoveValue(lp,section,strOutgoing4);
+		else if(!iniSetIpAddress(lp, section, strOutgoing4, mail->outgoing4.s_addr, &style))
+			break;
+
+		if(memcmp(&mail->outgoing6, &global->outgoing6, sizeof(ftp->outgoing6)))
+			iniRemoveValue(lp,section,strOutgoing6);
+		else if(!iniSetIp6Address(lp, section, strOutgoing6, mail->outgoing6, &style))
 			break;
 
 		if(mail->sem_chk_freq==global->sem_chk_freq)
@@ -1003,9 +1066,19 @@ BOOL sbbs_write_ini(
 		if(!iniSetBool(lp,section,strAutoStart,run_services,&style))
 			break;
 
-		if(services->interface_addr==global->interface_addr)
-			iniRemoveValue(lp,section,strInterface);
-		else if(!iniSetIpAddress(lp,section,strInterface,services->interface_addr,&style))
+		if(strListCmp(services->interfaces, global->interfaces)==0)
+			iniRemoveValue(lp,section,strInterfaces);
+		else if(!iniSetStringList(lp,section,strInterfaces,",",services->interfaces,&style))
+			break;
+
+		if(services->outgoing4.s_addr == global->outgoing4.s_addr)
+			iniRemoveValue(lp,section,strOutgoing4);
+		else if(!iniSetIpAddress(lp, section, strOutgoing4, services->outgoing4.s_addr, &style))
+			break;
+
+		if(memcmp(&services->outgoing6, &global->outgoing6, sizeof(ftp->outgoing6)))
+			iniRemoveValue(lp,section,strOutgoing6);
+		else if(!iniSetIp6Address(lp, section, strOutgoing6, services->outgoing6, &style))
 			break;
 
 		if(services->sem_chk_freq==global->sem_chk_freq)
@@ -1059,13 +1132,30 @@ BOOL sbbs_write_ini(
 		if(!iniSetBool(lp,section,strAutoStart,run_web,&style))
 			break;
 
-		if(web->interface_addr==global->interface_addr)
-			iniRemoveValue(lp,section,strInterface);
-		else if(!iniSetIpAddress(lp,section,strInterface,web->interface_addr,&style))
+		if(strListCmp(web->interfaces, global->interfaces)==0)
+			iniRemoveValue(lp,section,strInterfaces);
+		else if(!iniSetStringList(lp,section,strInterfaces,",",web->interfaces,&style))
+			break;
+
+		if(strListCmp(web->tls_interfaces, global->interfaces)==0)
+			iniRemoveValue(lp,section,"TLSInterface");
+		else if(!iniSetStringList(lp,section,"TLSInterface",",",web->tls_interfaces,&style))
+			break;
+
+		if(web->outgoing4.s_addr == global->outgoing4.s_addr)
+			iniRemoveValue(lp,section,strOutgoing4);
+		else if(!iniSetIpAddress(lp, section, strOutgoing4, web->outgoing4.s_addr, &style))
+			break;
+
+		if(memcmp(&web->outgoing6, &global->outgoing6, sizeof(ftp->outgoing6)))
+			iniRemoveValue(lp,section,strOutgoing6);
+		else if(!iniSetIp6Address(lp, section, strOutgoing6, web->outgoing6, &style))
 			break;
 
 		if(!iniSetShortInt(lp,section,strPort,web->port,&style))
 			break;
+		if(!iniSetShortInt(lp,section,"TLSPort",web->tls_port,&style))
+			break;
 		if(!iniSetShortInt(lp,section,strMaxClients,web->max_clients,&style))
 			break;
 		if(!iniSetShortInt(lp,section,strMaxInactivity,web->max_inactivity,&style))
diff --git a/src/sbbs3/sbbscon.c b/src/sbbs3/sbbscon.c
index 430f186691c1991119cb11321856149b43cdaf52..ad16fbb6460ccbfbc9f5f5b518794de3beb1b531 100644
--- a/src/sbbs3/sbbscon.c
+++ b/src/sbbs3/sbbscon.c
@@ -1165,7 +1165,11 @@ static void show_usage(char *cmd)
 /****************************************************************************/
 /* Main Entry Point															*/
 /****************************************************************************/
+#ifdef BUILD_JSDOCS
+int CIOLIB_main(int argc, char** argv)
+#else
 int main(int argc, char** argv)
+#endif
 {
 	int		i;
 	int		n;
@@ -2063,16 +2067,19 @@ int main(int argc, char** argv)
 						struct tm			tm;
 						list_node_t*		node;
 						login_attempt_t*	login_attempt;
+						char				ip_addr[INET6_ADDRSTRLEN];
 
 					    listLock(&login_attempt_list);
 						count=0;
 						for(node=login_attempt_list.first; node!=NULL; node=node->next) {
 							login_attempt=node->data;
 							localtime32(&login_attempt->time,&tm);
+							if(inet_addrtop(&login_attempt->addr, ip_addr, sizeof(ip_addr))==NULL)
+								strcpy(ip_addr, "<invalid address>");
 							printf("%lu attempts (%lu duplicate) from %s, last via %s on %u/%u %02u:%02u:%02u (user: %s, password: %s)\n"
 								,login_attempt->count
 								,login_attempt->dupes
-								,inet_ntoa(login_attempt->addr)
+								,ip_addr
 								,login_attempt->prot
 								,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec
 								,login_attempt->user
diff --git a/src/sbbs3/sbbsdefs.h b/src/sbbs3/sbbsdefs.h
index 02bad9dc813cf588311fe09a37c62a4a415f9155..dd8ae213e6e697e796254cfebfad0b5de2ea9160 100644
--- a/src/sbbs3/sbbsdefs.h
+++ b/src/sbbs3/sbbsdefs.h
@@ -50,10 +50,10 @@
 /* Constants */
 /*************/
 
-#define VERSION 	"3.16"  /* Version: Major.minor  */
-#define REVISION	'd'     /* Revision: lowercase letter */
-#define VERSION_NUM	(31600	 + (tolower(REVISION)-'a'))
-#define VERSION_HEX	(0x31600 + (tolower(REVISION)-'a'))
+#define VERSION 	"3.17"  /* Version: Major.minor  */
+#define REVISION	'a'     /* Revision: lowercase letter */
+#define VERSION_NUM	(31700	 + (tolower(REVISION)-'a'))
+#define VERSION_HEX	(0x31700 + (tolower(REVISION)-'a'))
 
 #define VERSION_NOTICE		"Synchronet BBS for "PLATFORM_DESC\
 								"  Version " VERSION
@@ -498,9 +498,9 @@ typedef enum {						/* Values for xtrn_t.event				*/
 #define LEN_TITLE		70	/* Message title								*/
 #define LEN_MAIN_CMD	34	/* Storage in user.dat for custom commands		*/
 #define LEN_XFER_CMD	40													
-#define LEN_SCAN_CMD	40													
-#define LEN_MAIL_CMD	40													
-#define LEN_CID 		25	/* Caller ID (phone number) 					*/
+#define LEN_SCAN_CMD	35													
+#define LEN_IPADDR	45													
+#define LEN_CID 		45	/* Caller ID (phone number) 					*/
 #define LEN_ARSTR		40	/* Max length of Access Requirement string		*/
 #define LEN_CHATACTCMD	 9	/* Chat action command							*/
 #define LEN_CHATACTOUT	65	/* Chat action output string					*/
@@ -566,8 +566,8 @@ typedef enum {						/* Values for xtrn_t.event				*/
 #define U_MAIN_CMD	U_CURXTRN+8+2 	/* unused */
 #define U_XFER_CMD	U_MAIN_CMD+LEN_MAIN_CMD 		/* unused */
 #define U_SCAN_CMD	U_XFER_CMD+LEN_XFER_CMD+2  	/* unused */
-#define U_MAIL_CMD	U_SCAN_CMD+LEN_SCAN_CMD 		/* unused */
-#define U_FREECDT	U_MAIL_CMD+LEN_MAIL_CMD+2 
+#define U_IPADDR	U_SCAN_CMD+LEN_SCAN_CMD 		/* unused */
+#define U_FREECDT	U_IPADDR+LEN_IPADDR+2 
 #define U_FLAGS3	U_FREECDT+10 	/* Flag set #3 */
 #define U_FLAGS4	U_FLAGS3+8 	/* Flag set #4 */
 #define U_XEDIT 	U_FLAGS4+8 	/* External editor (code  */
@@ -937,7 +937,8 @@ typedef struct {						/* Users information */
 			comment[LEN_COMMENT+1], 	/* Private comment about user */
 			cursub[LEN_EXTCODE+1],		/* Current sub-board internal code */
 			curdir[LEN_EXTCODE+1],		/* Current directory internal code */
-			curxtrn[9];					/* Current external program internal code */
+			curxtrn[9],					/* Current external program internal code */
+			ipaddr[LEN_IPADDR+1];		/* Last known IP address */
 
 	uchar	level,						/* Security level */
 			sex,						/* Sex - M or F */
diff --git a/src/sbbs3/services.c b/src/sbbs3/services.c
index fdfbd0eeb3ef18549aac51b2062c30ef53dfebae..4f0280524c04c1f043948807c138b1d66aaa95bb 100644
--- a/src/sbbs3/services.c
+++ b/src/sbbs3/services.c
@@ -61,6 +61,9 @@
 #include "sbbs_ini.h"
 #include "js_rtpool.h"
 #include "js_request.h"
+#include "js_socket.h"
+#include "multisock.h"
+#include "ssl.h"
 
 /* Constants */
 
@@ -79,37 +82,42 @@ static protected_uint32_t threads_pending_start;
 
 typedef struct {
 	/* These are sysop-configurable */
-	uint32_t	interface_addr;
-	uint16_t	port;
-	char		protocol[34];
-	char		cmd[128];
-	uint		max_clients;
-	uint32_t	options;
-	int			listen_backlog;
-	int			log_level;
-	uint32_t	stack_size;
+	uint32_t		interface_addr;
+	uint16_t		port;
+	str_list_t		interfaces;
+	struct in_addr		outgoing4;
+	struct in6_addr	outgoing6;
+	char			protocol[34];
+	char			cmd[128];
+	uint			max_clients;
+	uint32_t		options;
+	int				listen_backlog;
+	int				log_level;
+	uint32_t		stack_size;
 	js_startup_t	js;
 	js_server_props_t js_server_props;
 	/* These are run-time state and stat vars */
-	uint32_t	clients;
-	ulong		served;
-	SOCKET		socket;
-	BOOL		running;
-	BOOL		terminated;
+	uint32_t		clients;
+	ulong			served;
+	struct xpms_set	*set;
+	int				running;
+	BOOL			terminated;
 } service_t;
 
 typedef struct {
-	SOCKET			socket;
-	SOCKADDR_IN		addr;
-	time_t			logintime;
-	user_t			user;
-	client_t*		client;
-	service_t*		service;
-	js_callback_t	callback;
+	SOCKET				socket;
+	struct xpms_set		*set;
+	union xp_sockaddr	addr;
+	time_t				logintime;
+	user_t				user;
+	client_t*			client;
+	service_t*			service;
+	js_callback_t		callback;
 	/* Initial UDP datagram */
-	BYTE*			udp_buf;
-	int				udp_len;
-	subscan_t		*subscan;
+	BYTE*				udp_buf;
+	int					udp_len;
+	subscan_t			*subscan;
+	CRYPT_SESSION		tls_sess;
 } service_client_t;
 
 static service_t	*service=NULL;
@@ -169,6 +177,8 @@ static BOOL winsock_startup(void)
 
 #endif
 
+static CRYPT_CONTEXT tls_context = -1;
+
 static ulong active_clients(void)
 {
 	ulong i;
@@ -210,20 +220,32 @@ static void thread_down(void)
 		startup->thread_up(startup->cbdata,FALSE,FALSE);
 }
 
-static SOCKET open_socket(int type, const char* protocol)
+void open_socket_cb(SOCKET sock, void *serv_ptr)
 {
 	char	error[256];
 	char	section[128];
-	SOCKET	sock;
+	service_t	*serv=(service_t *)serv_ptr;
 
-	sock=socket(AF_INET, type, IPPROTO_IP);
-	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
+	if(startup!=NULL && startup->socket_open!=NULL)
 		startup->socket_open(startup->cbdata,TRUE);
-	if(sock!=INVALID_SOCKET) {
-		SAFEPRINTF(section,"services|%s", protocol);
-		if(set_socket_options(&scfg, sock, section, error, sizeof(error)))
-			lprintf(LOG_ERR,"%04d !ERROR %s",sock, error);
-	}
+	SAFEPRINTF(section,"services|%s", serv->protocol);
+	if(set_socket_options(&scfg, sock, section, error, sizeof(error)))
+		lprintf(LOG_ERR,"%04d !ERROR %s",sock, error);
+}
+
+void close_socket_cb(SOCKET sock, void *serv_ptr)
+{
+	if(startup!=NULL && startup->socket_open!=NULL)
+		startup->socket_open(startup->cbdata,FALSE);
+}
+
+static SOCKET open_socket(int family, int type, service_t* serv)
+{
+	SOCKET	sock;
+
+	sock=socket(family, type, IPPROTO_IP);
+	if(sock!=INVALID_SOCKET)
+		open_socket_cb(sock, serv);
 	return(sock);
 }
 
@@ -252,171 +274,6 @@ static void status(char* str)
 
 /* Global JavaScript Methods */
 
-static JSBool
-js_read(JSContext *cx, uintN argc, jsval *arglist)
-{
-	jsval *argv=JS_ARGV(cx, arglist);
-	char*		buf;
-	int32		len=512;
-	service_client_t* client;
-	jsrefcount	rc;
-
-	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
-
-	if((client=(service_client_t*)JS_GetContextPrivate(cx))==NULL)
-		return(JS_FALSE);
-
-	if(argc) {
-		if(!JS_ValueToInt32(cx,argv[0],&len))
-			return JS_FALSE;
-	}
-	
-	if((buf=malloc(len))==NULL)
-		return(JS_FALSE);
-
-	rc=JS_SUSPENDREQUEST(cx);
-	len=recv(client->socket,buf,len,0);
-	JS_RESUMEREQUEST(cx, rc);
-
-	if(len>0)
-		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyN(cx,buf,len)));
-	free(buf);
-
-	return(JS_TRUE);
-}
-
-static JSBool
-js_readln(JSContext *cx, uintN argc, jsval *arglist)
-{
-	jsval *argv=JS_ARGV(cx, arglist);
-	char		ch;
-	char*		buf;
-	int			i;
-	int32		len=512;
-	BOOL		rd;
-	time_t		start;
-	int32		timeout=30;	/* seconds */
-	JSString*	str;
-	service_client_t* client;
-	jsrefcount	rc;
-
-	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
-
-	if((client=(service_client_t*)JS_GetContextPrivate(cx))==NULL)
-		return(JS_FALSE);
-
-	if(argc) {
-		if(!JS_ValueToInt32(cx,argv[0],&len))
-			return JS_FALSE;
-	}
-
-	if((buf=(char*)malloc(len+1))==NULL) {
-		JS_ReportError(cx,"Error allocating %u bytes",len+1);
-		return(JS_FALSE);
-	}
-
-	if(argc>1) {
-		if(!JS_ValueToInt32(cx,argv[1],(int32*)&timeout)) {
-			free(buf);
-			return JS_FALSE;
-		}
-	}
-
-	rc=JS_SUSPENDREQUEST(cx);
-	start=time(NULL);
-	for(i=0;i<len;) {
-
-		if(!socket_check(client->socket,&rd,NULL,1000))
-			break;		/* disconnected */
-
-		if(!rd) {
-			if(time(NULL)-start>timeout) {
-				JS_SET_RVAL(cx, arglist, JSVAL_NULL);
-				JS_RESUMEREQUEST(cx, rc);
-				free(buf);
-				return(JS_TRUE);	/* time-out */
-			}
-			continue;	/* no data */
-		}
-
-		if(recv(client->socket, &ch, 1, 0)!=1)
-			break;
-
-		if(ch=='\n' /* && i>=1 */) /* Mar-9-2003: terminate on sole LF */
-			break;
-
-		buf[i++]=ch;
-	}
-	if(i>0 && buf[i-1]=='\r')
-		buf[i-1]=0;
-	else
-		buf[i]=0;
-	JS_RESUMEREQUEST(cx, rc);
-
-	str = JS_NewStringCopyZ(cx, buf);
-	free(buf);
-	if(str==NULL)
-		return(JS_FALSE);
-
-	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
-		
-	return(JS_TRUE);
-}
-
-static JSBool
-js_write(JSContext *cx, uintN argc, jsval *arglist)
-{
-	jsval *argv=JS_ARGV(cx, arglist);
-	uintN		i;
-	char*		cp=NULL;
-	size_t		cp_sz=0;
-	size_t		len;
-	service_client_t* client;
-	jsrefcount	rc;
-
-	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
-
-	if((client=(service_client_t*)JS_GetContextPrivate(cx))==NULL)
-		return(JS_FALSE);
-
-	JS_SET_RVAL(cx, arglist, argv[0]);
-
-	for(i=0; i<argc; i++) {
-		JSVALUE_TO_RASTRING(cx, argv[i], cp, &cp_sz, &len);
-		if(cp==NULL)
-			continue;
-		rc=JS_SUSPENDREQUEST(cx);
-		sendsocket(client->socket,cp,len);
-		JS_RESUMEREQUEST(cx, rc);
-	}
-	if(cp)
-		free(cp);
-
-	return(JS_TRUE);
-}
-
-static JSBool
-js_writeln(JSContext *cx, uintN argc, jsval *arglist)
-{
-	char*		cp;
-	service_client_t* client;
-	jsrefcount	rc;
-
-	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
-
-	if((client=(service_client_t*)JS_GetContextPrivate(cx))==NULL)
-		return(JS_FALSE);
-	
-	js_write(cx,argc,arglist);
-
-	rc=JS_SUSPENDREQUEST(cx);
-	cp="\r\n";
-	sendsocket(client->socket,cp,2);
-	JS_RESUMEREQUEST(cx, rc);
-
-	return(JS_TRUE);
-}
-
 static JSBool
 js_log(JSContext *cx, uintN argc, jsval *arglist)
 {
@@ -464,18 +321,21 @@ js_log(JSContext *cx, uintN argc, jsval *arglist)
     return(JS_TRUE);
 }
 
-static void badlogin(SOCKET sock, char* prot, char* user, char* passwd, char* host, SOCKADDR_IN* addr)
+static void badlogin(SOCKET sock, char* prot, char* user, char* passwd, char* host, union xp_sockaddr* addr)
 {
 	char reason[128];
+	char addr_ip[INET6_ADDRSTRLEN];
 	ulong count;
 
 	SAFEPRINTF(reason,"%s LOGIN", prot);
 	count=loginFailure(startup->login_attempt_list, addr, prot, user, passwd);
 	if(startup->login_attempt_hack_threshold && count>=startup->login_attempt_hack_threshold)
 		hacklog(&scfg, reason, user, passwd, host, addr);
-	if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold)
+	if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold) {
+		inet_addrtop(addr, addr_ip, sizeof(addr_ip));
 		filter_ip(&scfg, prot, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
-			,host, inet_ntoa(addr->sin_addr), user, /* fname: */NULL);
+			,host, addr_ip, user, /* fname: */NULL);
+	}
 
 	mswait(startup->login_attempt_delay);
 }
@@ -551,7 +411,7 @@ js_login(JSContext *cx, uintN argc, jsval *arglist)
 
 	rc=JS_SUSPENDREQUEST(cx);
 	if(client->client!=NULL) {
-		SAFECOPY(client->user.note,client->client->addr);
+		SAFECOPY(client->user.ipaddr,client->client->addr);
 		SAFECOPY(client->user.comp,client->client->host);
 		SAFECOPY(client->user.modem,client->service->protocol);
 	}
@@ -633,11 +493,6 @@ js_logout(JSContext *cx, uintN argc, jsval *arglist)
 }
 
 static JSFunctionSpec js_global_functions[] = {
-	{"read",			js_read,			0},		/* read from client socket */
-	{"readln",			js_readln,			0},		/* read line from client socket */
-	{"write",			js_write,			0},		/* write to client socket */
-	{"writeln",			js_writeln,			0},		/* write line to client socket */
-	{"print",			js_writeln,			0},		/* write line to client socket */
 	{"log",				js_log,				0},		/* Log a string */
  	{"login",			js_login,			2},		/* Login specified username and password */
 	{"logout",			js_logout,			0},		/* Logout user */
@@ -698,13 +553,13 @@ static JSBool
 js_client_add(JSContext *cx, uintN argc, jsval *arglist)
 {
 	jsval *argv=JS_ARGV(cx, arglist);
-	client_t	client;
-	SOCKET		sock=INVALID_SOCKET;
-	socklen_t	addr_len;
-	SOCKADDR_IN	addr;
+	client_t		client;
+	SOCKET			sock=INVALID_SOCKET;
+	socklen_t		addr_len;
+	union xp_sockaddr	addr;
 	service_client_t* service_client;
-	jsrefcount	rc;
-	char		*cstr=NULL;
+	jsrefcount		rc;
+	char			*cstr=NULL;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
@@ -721,13 +576,13 @@ js_client_add(JSContext *cx, uintN argc, jsval *arglist)
 	client.time=time32(NULL);
 	client.user="<unknown>";
 	SAFECOPY(client.host,client.user);
-	
+
 	sock=js_socket(cx,argv[0]);
-	
+
 	addr_len = sizeof(addr);
-	if(getpeername(sock, (struct sockaddr *)&addr, &addr_len)==0) {
-		SAFECOPY(client.addr,inet_ntoa(addr.sin_addr));
-		client.port=ntohs(addr.sin_port);
+	if(getpeername(sock, &addr.addr, &addr_len)==0) {
+		inet_addrtop(&addr, client.addr, sizeof(client.addr));
+		client.port=inet_addrport(&addr);
 	}
 
 	if(argc>1) {
@@ -742,8 +597,8 @@ js_client_add(JSContext *cx, uintN argc, jsval *arglist)
 	rc=JS_SUSPENDREQUEST(cx);
 	client_on(sock, &client, /* update? */ FALSE);
 #ifdef _DEBUG
-	lprintf(LOG_DEBUG,"%04d %s client_add(%04u,%s,%s)"
-		,service_client->service->socket,service_client->service->protocol
+	lprintf(LOG_DEBUG,"%s client_add(%04u,%s,%s)"
+		,service_client->service->protocol
 		,sock,client.user,client.host);
 #endif
 	if(cstr)
@@ -759,8 +614,8 @@ js_client_update(JSContext *cx, uintN argc, jsval *arglist)
 	client_t	client;
 	SOCKET		sock=INVALID_SOCKET;
 	socklen_t	addr_len;
-	SOCKADDR_IN	addr;
-	service_client_t* service_client;
+	union xp_sockaddr	addr;
+	service_client_t*	service_client;
 	jsrefcount	rc;
 	char		*cstr=NULL;
 
@@ -778,9 +633,9 @@ js_client_update(JSContext *cx, uintN argc, jsval *arglist)
 	sock=js_socket(cx,argv[0]);
 
 	addr_len = sizeof(addr);
-	if(getpeername(sock, (struct sockaddr *)&addr, &addr_len)==0) {
-		SAFECOPY(client.addr,inet_ntoa(addr.sin_addr));
-		client.port=ntohs(addr.sin_port);
+	if(getpeername(sock, &addr.addr, &addr_len)==0) {
+		inet_addrtop(&addr, client.addr, sizeof(client.addr));
+		client.port=inet_addrport(&addr);
 	}
 
 	if(argc>1) {
@@ -794,8 +649,8 @@ js_client_update(JSContext *cx, uintN argc, jsval *arglist)
 	rc=JS_SUSPENDREQUEST(cx);
 	client_on(sock, &client, /* update? */ TRUE);
 #ifdef _DEBUG
-	lprintf(LOG_DEBUG,"%04d %s client_update(%04u,%s,%s)"
-		,service_client->service->socket,service_client->service->protocol
+	lprintf(LOG_DEBUG,"%s client_update(%04u,%s,%s)"
+		,service_client->service->protocol
 		,sock,client.user,client.host);
 #endif
 	if(cstr)
@@ -826,8 +681,8 @@ js_client_remove(JSContext *cx, uintN argc, jsval *arglist)
 		client_off(sock);
 
 		if(service_client->service->clients==0)
-			lprintf(LOG_WARNING,"%04d %s !client_remove() called with 0 service clients"
-				,service_client->service->socket, service_client->service->protocol);
+			lprintf(LOG_WARNING,"%s !client_remove() called with 0 service clients"
+				,service_client->service->protocol);
 		else {
 			service_client->service->clients--;
 			update_clients();
@@ -836,8 +691,8 @@ js_client_remove(JSContext *cx, uintN argc, jsval *arglist)
 	}
 
 #ifdef _DEBUG
-	lprintf(LOG_DEBUG,"%04d %s client_remove(%04u)"
-		,service_client->service->socket, service_client->service->protocol, sock);
+	lprintf(LOG_DEBUG,"%s client_remove(%04u)"
+		,service_client->service->protocol, sock);
 #endif
 	return(JS_TRUE);
 }
@@ -849,6 +704,10 @@ js_initcx(JSRuntime* js_runtime, SOCKET sock, service_client_t* service_client,
 	JSObject*	server;
 	BOOL		success=FALSE;
 	BOOL		rooted=FALSE;
+	jsval		val;
+	JSObject*	obj;
+	JSObject*	socket_obj;
+	js_socket_private_t* p;
 
     if((js_cx = JS_NewContext(js_runtime, service_client->service->js.cx_stack))==NULL)
 		return(NULL);
@@ -874,9 +733,39 @@ js_initcx(JSRuntime* js_runtime, SOCKET sock, service_client_t* service_client,
 			break;
 
 		/* Client Object */
-		if(service_client->client!=NULL)
+		if(service_client->client!=NULL) {
 			if(js_CreateClientObject(js_cx, *glob, "client", service_client->client, sock)==NULL)
 				break;
+			/* Copy client socket stuff into the global context */
+			if (!JS_GetProperty(js_cx, *glob, "client", &val) || val == JSVAL_VOID)
+				break;
+			obj=JSVAL_TO_OBJECT(val);
+			if (!JS_GetProperty(js_cx, obj, "socket", &val) || val == JSVAL_VOID)
+				break;
+			socket_obj=JSVAL_TO_OBJECT(val);
+			if (service_client->service->options & SERVICE_OPT_TLS) {
+				p=(js_socket_private_t*)JS_GetPrivate(js_cx,socket_obj);
+				p->session=service_client->tls_sess;
+			}
+			if (!JS_GetProperty(js_cx, socket_obj, "read", &val) || val == JSVAL_VOID)
+				break;
+			if (!JS_DefineProperty(js_cx, *glob, "read", val, NULL, NULL, JSPROP_ENUMERATE))
+				break;
+			if (!JS_GetProperty(js_cx, socket_obj, "readln", &val) || val == JSVAL_VOID)
+				break;
+			if (!JS_DefineProperty(js_cx, *glob, "readln", val, NULL, NULL, JSPROP_ENUMERATE))
+				break;
+			if (!JS_GetProperty(js_cx, socket_obj, "write", &val) || val == JSVAL_VOID)
+				break;
+			if (!JS_DefineProperty(js_cx, *glob, "write", val, NULL, NULL, JSPROP_ENUMERATE))
+				break;
+			if (!JS_GetProperty(js_cx, socket_obj, "writeln", &val) || val == JSVAL_VOID)
+				break;
+			if (!JS_DefineProperty(js_cx, *glob, "writeln", val, NULL, NULL, JSPROP_ENUMERATE))
+				break;
+			if (!JS_DefineProperty(js_cx, *glob, "print", val, NULL, NULL, JSPROP_ENUMERATE))
+				break;
+		}
 
 		/* User Class */
 		if(js_CreateUserClass(js_cx, *glob, &scfg)==NULL) 
@@ -912,33 +801,6 @@ js_initcx(JSRuntime* js_runtime, SOCKET sock, service_client_t* service_client,
 
 		if(js_CreateSystemObject(js_cx, *glob, &scfg, uptime, startup->host_name, SOCKLIB_DESC)==NULL) 
 			break;
-#if 0		
-		char		ver[256];
-		JSString*	js_str;
-		jsval		val;
-
-		/* server object */
-		if((server=JS_DefineObject(js_cx, *glob, "server", &js_server_class
-			,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))==NULL)
-			break;
-
-		if(!JS_DefineProperties(js_cx, server, js_server_properties))
-			break;
-
-		sprintf(ver,"Synchronet Services %s",revision);
-		if((js_str=JS_NewStringCopyZ(js_cx, ver))==NULL)
-			break;
-		val = STRING_TO_JSVAL(js_str);
-		if(!JS_SetProperty(js_cx, server, "version", &val))
-			break;
-
-		if((js_str=JS_NewStringCopyZ(js_cx, services_ver()))==NULL)
-			break;
-		val = STRING_TO_JSVAL(js_str);
-		if(!JS_SetProperty(js_cx, server, "version_detail", &val))
-			break;
-
-#else
 
 		if(service_client->service->js_server_props.version[0]==0) {
 			SAFEPRINTF(service_client->service->js_server_props.version
@@ -956,11 +818,11 @@ js_initcx(JSRuntime* js_runtime, SOCKET sock, service_client_t* service_client,
 		if((server=js_CreateServerObject(js_cx,*glob
 			,&service_client->service->js_server_props))==NULL)
 			break;
-#endif
 
-		if(service_client->client==NULL)	/* static service */
-			if(js_CreateSocketObject(js_cx, server, "socket", service_client->socket)==NULL)
+		if(service_client->client==NULL) {	/* static service */
+			if(js_CreateSocketObjectFromSet(js_cx, server, "socket", service_client->set)==NULL)
 				break;
+		}
 
 		JS_DefineFunction(js_cx, server, "client_add"	, js_client_add,	1, 0);
 		JS_DefineFunction(js_cx, server, "client_update", js_client_update,	1, 0);
@@ -1048,10 +910,41 @@ static void js_init_args(JSContext* js_cx, JSObject* js_obj, const char* cmdline
 		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
 }
 
+#define HANDLE_CRYPT_CALL(status, service_client)  handle_crypt_call(status, service_client, __FILE__, __LINE__)
+
+static BOOL handle_crypt_call(int status, service_client_t *service_client, const char *file, int line)
+{
+	int		len = 0;
+	char	estr[CRYPT_MAX_TEXTSIZE+1];
+	int		sock = 0;
+
+	if (status == CRYPT_OK)
+		return TRUE;
+	if (service_client != NULL) {
+		if (service_client->service->options & SERVICE_OPT_TLS)
+			cryptGetAttributeString(service_client->tls_sess, CRYPT_ATTRIBUTE_ERRORMESSAGE, estr, &len);
+		sock = service_client->socket;
+	}
+	estr[len]=0;
+	if (len)
+		lprintf(LOG_ERR, "%04d cryptlib error %d at %s:%d (%s)", sock, status, file, line, estr);
+	else
+		lprintf(LOG_ERR, "%04d cryptlib error %d at %s:%d", sock, status, file, line);
+	return FALSE;
+}
+
+static void js_service_failure_cleanup(service_t *service, SOCKET socket)
+{
+	close_socket(socket);
+	if(service->clients)
+		service->clients--;
+	thread_down();
+	return;
+}
+
 static void js_service_thread(void* arg)
 {
-	char*					host_name;
-	HOSTENT*				host;
+	char					host_name[256];
 	SOCKET					socket;
 	client_t				client;
 	service_t*				service;
@@ -1084,28 +977,17 @@ static void js_service_thread(void* arg)
 
 	/* Host name lookup and filtering */
 	if(service->options&BBS_OPT_NO_HOST_LOOKUP 
-		|| startup->options&BBS_OPT_NO_HOST_LOOKUP)
-		host=NULL;
-	else
-		host=gethostbyaddr((char *)&service_client.addr.sin_addr
-			,sizeof(service_client.addr.sin_addr),AF_INET);
-
-	if(host!=NULL && host->h_name!=NULL)
-		host_name=host->h_name;
-	else
-		host_name="<no name>";
+			|| startup->options&BBS_OPT_NO_HOST_LOOKUP)
+		strcpy(host_name, "<no name>");
+	else {
+		if(getnameinfo(&service_client.addr.addr, xp_sockaddr_len(&service_client), host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD) != 0)
+			strcpy(host_name, "<no name>");
+	}
 
 	if(!(service->options&BBS_OPT_NO_HOST_LOOKUP)
 		&& !(startup->options&BBS_OPT_NO_HOST_LOOKUP)) {
 		lprintf(LOG_INFO,"%04d %s Hostname: %s"
 			,socket, service->protocol, host_name);
-#if	0 /* gethostbyaddr() is apparently not (always) thread-safe
-	     and getnameinfo() doesn't return alias information */
-		for(i=0;host!=NULL && host->h_aliases!=NULL 
-			&& host->h_aliases[i]!=NULL;i++)
-			lprintf(LOG_INFO,"%04d %s HostAlias: %s"
-				,socket, service->protocol, host->h_aliases[i]);
-#endif
 	}
 
 	if(trashcan(&scfg,host_name,"host")) {
@@ -1118,12 +1000,44 @@ static void js_service_thread(void* arg)
 		return;
 	}
 
+	if (service_client.service->options & SERVICE_OPT_TLS) {
+		/* Create and initialize the TLS session */
+		if (!HANDLE_CRYPT_CALL(cryptCreateSession(&service_client.tls_sess, CRYPT_UNUSED, CRYPT_SESSION_SSL_SERVER), &service_client)) {
+			js_service_failure_cleanup(service, socket);
+			return;
+		}
+		/* Add all the user/password combinations */
+#if 0 // TLS-PSK is currently broken in cryptlib
+		last = lastuser(&scfg);
+		for (i=1; i <= last; i++) {
+			user.number = i;
+			getuserdat(&scfg,&user);
+			if(user.misc&(DELETED|INACTIVE))
+				continue;
+			if (user.alias[0] && user.pass[0]) {
+				if(HANDLE_CRYPT_CALL(cryptSetAttributeString(service_client.tls_sess, CRYPT_SESSINFO_USERNAME, user.alias, strlen(user.alias)), &session))
+					HANDLE_CRYPT_CALL(cryptSetAttributeString(service_client.tls_sess, CRYPT_SESSINFO_PASSWORD, user.pass, strlen(user.pass)), &session);
+			}
+		}
+#endif
+		if (tls_context != -1) {
+			HANDLE_CRYPT_CALL(cryptSetAttribute(service_client.tls_sess, CRYPT_SESSINFO_PRIVATEKEY, tls_context), &service_client);
+		}
+		BOOL nodelay=TRUE;
+		setsockopt(socket,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
+
+		HANDLE_CRYPT_CALL(cryptSetAttribute(service_client.tls_sess, CRYPT_SESSINFO_NETWORKSOCKET, socket), &service_client);
+		if (!HANDLE_CRYPT_CALL(cryptSetAttribute(service_client.tls_sess, CRYPT_SESSINFO_ACTIVE, 1), &service_client)) {
+			js_service_failure_cleanup(service, socket);
+			return;
+		}
+	}
 
 #if 0	/* Need to export from SBBS.DLL */
 	identity=NULL;
 	if(service->options&BBS_OPT_GET_IDENT 
 		&& startup->options&BBS_OPT_GET_IDENT) {
-		identify(&service_client.addr, service->port, str, sizeof(str)-1);
+		identify(&service_client, service->port, str, sizeof(str)-1);
 		identity=strrchr(str,':');
 		if(identity!=NULL) {
 			identity++;	/* skip colon */
@@ -1136,9 +1050,9 @@ static void js_service_thread(void* arg)
 
 	client.size=sizeof(client);
 	client.time=time32(NULL);
-	SAFECOPY(client.addr,inet_ntoa(service_client.addr.sin_addr));
+	inet_addrtop(&service_client.addr, client.addr, sizeof(client.addr));
 	SAFECOPY(client.host,host_name);
-	client.port=ntohs(service_client.addr.sin_port);
+	client.port=inet_addrport(&service_client.addr);
 	client.protocol=service->protocol;
 	client.user="<unknown>";
 	service_client.client=&client;
@@ -1163,7 +1077,7 @@ static void js_service_thread(void* arg)
 	if(startup->login_attempt_throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &service_client.addr)) > 1) {
 		lprintf(LOG_DEBUG,"%04d %s Throttling suspicious connection from: %s (%u login attempts)"
-			,socket, service->protocol, inet_ntoa(service_client.addr.sin_addr), login_attempts);
+			,socket, service->protocol, client.addr, login_attempts);
 		mswait(login_attempts*startup->login_attempt_throttle);
 	}
 
@@ -1243,7 +1157,7 @@ static void js_static_service_thread(void* arg)
 	char					fname[MAX_PATH+1];
 	service_t*				service;
 	service_client_t		service_client;
-	SOCKET					socket;
+	struct xpms_set			*set;
 	/* JavaScript-specific */
 	JSObject*				js_glob;
 	JSObject*				js_script;
@@ -1256,16 +1170,16 @@ static void js_static_service_thread(void* arg)
 	service=(service_t*)arg;
 
 	service->running=TRUE;
-	socket = service->socket;
+	set = service->set;
 
-	lprintf(LOG_DEBUG,"%04d %s static JavaScript service thread started", service->socket, service->protocol);
+	lprintf(LOG_DEBUG,"%s static JavaScript service thread started", service->protocol);
 
 	SetThreadName("JS Static Service");
 	thread_up(TRUE /* setuid */);
 	protected_uint32_adjust(&threads_pending_start, -1);
 
 	memset(&service_client,0,sizeof(service_client));
-	service_client.socket = service->socket;
+	service_client.set = service->set;
 	service_client.service = service;
 	service_client.callback.limit = service->js.time_limit;
 	service_client.callback.gc_interval = service->js.gc_interval;
@@ -1274,10 +1188,10 @@ static void js_static_service_thread(void* arg)
 	service_client.callback.auto_terminate = TRUE;
 
 	if((js_runtime=jsrt_GetNew(service->js.max_bytes, 5000, __FILE__, __LINE__))==NULL) {
-		lprintf(LOG_ERR,"%04d !%s ERROR initializing JavaScript runtime"
-			,service->socket,service->protocol);
-		close_socket(service->socket);
-		service->socket=INVALID_SOCKET;
+		lprintf(LOG_ERR,"!%s ERROR initializing JavaScript runtime"
+			,service->protocol);
+		xpms_destroy(service->set, close_socket_cb, service);
+		service->set = NULL;
 		thread_down();
 		return;
 	}
@@ -1289,9 +1203,9 @@ static void js_static_service_thread(void* arg)
 		sprintf(spath,"%s%s",scfg.exec_dir,fname);
 
 	do {
-		if((js_cx=js_initcx(js_runtime,service->socket,&service_client,&js_glob))==NULL) {
-			lprintf(LOG_ERR,"%04d !%s ERROR initializing JavaScript context"
-				,service->socket,service->protocol);
+		if((js_cx=js_initcx(js_runtime,0,&service_client,&js_glob))==NULL) {
+			lprintf(LOG_ERR,"!%s ERROR initializing JavaScript context"
+				,service->protocol);
 			break;
 		}
 
@@ -1303,7 +1217,7 @@ static void js_static_service_thread(void* arg)
 		JS_SetOperationCallback(js_cx, js_OperationCallback);
 	
 		if((js_script=JS_CompileFile(js_cx, js_glob, spath))==NULL)  {
-			lprintf(LOG_ERR,"%04d !JavaScript FAILED to compile script (%s)",service->socket,spath);
+			lprintf(LOG_ERR,"!JavaScript FAILED to compile script (%s)",spath);
 			break;
 		}
 
@@ -1325,35 +1239,39 @@ static void js_static_service_thread(void* arg)
 	jsrt_Release(js_runtime);
 
 	if(service->clients) {
-		lprintf(LOG_WARNING,"%04d %s !service terminating with %u active clients"
-			,socket, service->protocol, service->clients);
+		lprintf(LOG_WARNING,"%s !service terminating with %u active clients"
+			, service->protocol, service->clients);
 		service->clients=0;
 	}
 
 	thread_down();
-	lprintf(LOG_INFO,"%04d %s service thread terminated (%lu clients served)"
-		,socket, service->protocol, service->served);
+	lprintf(LOG_INFO,"%s service thread terminated (%lu clients served)"
+		, service->protocol, service->served);
 
-	close_socket(service->socket);
-	service->socket=INVALID_SOCKET;
+	xpms_destroy(service->set, close_socket_cb, service);
+	service->set = NULL;
 
 	service->running=FALSE;
 }
 
+struct native_service_instance {
+	service_t	*service;
+	SOCKET		socket;
+};
+
 static void native_static_service_thread(void* arg)
 {
 	char					cmd[MAX_PATH];
 	char					fullcmd[MAX_PATH*2];
-	SOCKET					socket;
 	SOCKET					socket_dup;
-	service_t*				service;
+	struct native_service_instance inst;
 
-	service = (service_t*)arg;
+	inst = *(struct native_service_instance *)arg;
+	free(arg);
 
-	service->running=TRUE;
-	socket = service->socket;
+	inst.service->running++;
 
-	lprintf(LOG_DEBUG,"%04d %s static service thread started", socket, service->protocol);
+	lprintf(LOG_DEBUG,"%04d %s static service thread started", inst.socket, inst.service->protocol);
 
 	SetThreadName("Static Service");
 	thread_up(TRUE /* setuid */);
@@ -1361,51 +1279,49 @@ static void native_static_service_thread(void* arg)
 
 #ifdef _WIN32
 	if(!DuplicateHandle(GetCurrentProcess(),
-		(HANDLE)socket,
-		GetCurrentProcess(),
-		(HANDLE*)&socket_dup,
-		0,
-		TRUE, /* Inheritable */
-		DUPLICATE_SAME_ACCESS)) {
+			(HANDLE)inst.socket,
+			GetCurrentProcess(),
+			(HANDLE*)&socket_dup,
+			0,
+			TRUE, /* Inheritable */
+			DUPLICATE_SAME_ACCESS)) {
 		lprintf(LOG_ERR,"%04d !%s ERROR %d duplicating socket descriptor"
-			,socket,service->protocol,GetLastError());
-		close_socket(service->socket);
-		service->socket=INVALID_SOCKET;
+			,inst.socket,inst.service->protocol,GetLastError());
+		close_socket(inst.socket);
 		thread_down();
+		inst.service->running--;
 		return;
 	}
 #else
-	socket_dup = dup(service->socket);
+	socket_dup = dup(inst.socket);
 #endif
 
 	/* RUN SCRIPT */
-	if(strpbrk(service->cmd,"/\\")==NULL)
-		sprintf(cmd,"%s%s",scfg.exec_dir,service->cmd);
+	if(strpbrk(inst.service->cmd,"/\\")==NULL)
+		sprintf(cmd,"%s%s",scfg.exec_dir,inst.service->cmd);
 	else
-		strcpy(cmd,service->cmd);
+		strcpy(cmd,inst.service->cmd);
 	sprintf(fullcmd,cmd,socket_dup);
 	
 	do {
 		system(fullcmd);
-	} while(!service->terminated && service->options&SERVICE_OPT_STATIC_LOOP);
+	} while(!inst.service->terminated && inst.service->options&SERVICE_OPT_STATIC_LOOP);
 
 	thread_down();
 	lprintf(LOG_INFO,"%04d %s service thread terminated (%lu clients served)"
 		,socket, service->protocol, service->served);
 
-	close_socket(service->socket);
-	service->socket=INVALID_SOCKET;
+	close_socket(inst.socket);
 	closesocket(socket_dup);	/* close duplicate handle */
 
-	service->running=FALSE;
+	service->running--;
 }
 
 static void native_service_thread(void* arg)
 {
 	char					cmd[MAX_PATH];
 	char					fullcmd[MAX_PATH*2];
-	char*					host_name;
-	HOSTENT*				host;
+	char					host_name[256];
 	SOCKET					socket;
 	SOCKET					socket_dup;
 	client_t				client;
@@ -1426,16 +1342,11 @@ static void native_service_thread(void* arg)
 
 	/* Host name lookup and filtering */
 	if(service->options&BBS_OPT_NO_HOST_LOOKUP 
-		|| startup->options&BBS_OPT_NO_HOST_LOOKUP)
-		host=NULL;
-	else
-		host=gethostbyaddr((char *)&service_client.addr.sin_addr
-			,sizeof(service_client.addr.sin_addr),AF_INET);
-
-	if(host!=NULL && host->h_name!=NULL)
-		host_name=host->h_name;
-	else
-		host_name="<no name>";
+			|| startup->options&BBS_OPT_NO_HOST_LOOKUP)
+		strcpy(host_name, "<no name>");
+	else 
+		if(getnameinfo(&service_client.addr.addr, xp_sockaddr_len(&service_client), host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD)!=0)
+			strcpy(host_name, "<no name>");
 
 	if(!(service->options&BBS_OPT_NO_HOST_LOOKUP)
 		&& !(startup->options&BBS_OPT_NO_HOST_LOOKUP)) {
@@ -1465,7 +1376,7 @@ static void native_service_thread(void* arg)
 	identity=NULL;
 	if(service->options&BBS_OPT_GET_IDENT 
 		&& startup->options&BBS_OPT_GET_IDENT) {
-		identify(&service_client.addr, service->port, str, sizeof(str)-1);
+		identify(&service_client, service->port, str, sizeof(str)-1);
 		identity=strrchr(str,':');
 		if(identity!=NULL) {
 			identity++;	/* skip colon */
@@ -1478,9 +1389,9 @@ static void native_service_thread(void* arg)
 
 	client.size=sizeof(client);
 	client.time=time32(NULL);
-	SAFECOPY(client.addr,inet_ntoa(service_client.addr.sin_addr));
+	inet_addrtop(&service_client.addr, client.addr, sizeof(client.addr));
 	SAFECOPY(client.host,host_name);
-	client.port=ntohs(service_client.addr.sin_port);
+	client.port=inet_addrport(&service_client.addr);
 	client.protocol=service->protocol;
 	client.user="<unknown>";
 
@@ -1510,7 +1421,7 @@ static void native_service_thread(void* arg)
 	if(startup->login_attempt_throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &service_client.addr)) > 1) {
 		lprintf(LOG_DEBUG,"%04d %s Throttling suspicious connection from: %s (%u login attempts)"
-			,socket, service->protocol, inet_ntoa(service_client.addr.sin_addr), login_attempts);
+			,socket, service->protocol, client.addr, login_attempts);
 		mswait(login_attempts*startup->login_attempt_throttle);
 	}
 
@@ -1573,6 +1484,7 @@ static service_t* read_services_ini(const char* services_ini, service_t* service
 	uint		max_clients;
 	uint32_t	options;
 	uint32_t	stack_size;
+	char		*default_interfaces;
 
 	if((fp=fopen(services_ini,"r"))==NULL) {
 		lprintf(LOG_CRIT,"!ERROR %d opening %s", errno, services_ini);
@@ -1592,6 +1504,7 @@ static service_t* read_services_ini(const char* services_ini, service_t* service
 
 	/* Enumerate and parse each service configuration */
 	sec_list = iniGetSectionList(list,"");
+	default_interfaces = strListCombine(startup->interfaces, NULL, 16384, ",");
     for(i=0; sec_list!=NULL && sec_list[i]!=NULL; i++) {
 		if(!iniGetBool(list,sec_list[i],"Enabled",TRUE)) {
 			lprintf(LOG_WARNING,"Ignoring disabled service: %s",sec_list[i]);
@@ -1599,8 +1512,10 @@ static service_t* read_services_ini(const char* services_ini, service_t* service
 		}
 		memset(&serv,0,sizeof(service_t));
 		SAFECOPY(serv.protocol,iniGetString(list,sec_list[i],"Protocol",sec_list[i],prot));
-		serv.socket=INVALID_SOCKET;
-		serv.interface_addr=iniGetIpAddress(list,sec_list[i],"Interface",startup->interface_addr);
+		serv.set = NULL;
+		serv.interfaces=iniGetStringList(list,sec_list[i],"Interface",",",default_interfaces);
+		serv.outgoing4.s_addr=iniGetIpAddress(list,sec_list[i],"OutgoingV4",startup->outgoing4.s_addr);
+		serv.outgoing6=iniGetIp6Address(list,sec_list[i],"OutgoingV6",startup->outgoing6);
 		serv.max_clients=iniGetInteger(list,sec_list[i],"MaxClients",max_clients);
 		serv.listen_backlog=iniGetInteger(list,sec_list[i],"ListenBacklog",listen_backlog);
 		serv.stack_size=(uint32_t)iniGetBytes(list,sec_list[i],"StackSize",1,stack_size);
@@ -1654,12 +1569,14 @@ static service_t* read_services_ini(const char* services_ini, service_t* service
 		if((np=(service_t*)realloc(service,sizeof(service_t)*((*services)+1)))==NULL) {
 			fclose(fp);
 			lprintf(LOG_CRIT,"!MALLOC FAILURE");
+			free(default_interfaces);
 			return(service);
 		}
 		service=np;
 		service[*services]=serv;
 		(*services)++;
 	}
+	free(default_interfaces);
 	iniFreeStringList(sec_list);
 	strListFree(&list);
 
@@ -1682,6 +1599,11 @@ static void cleanup(int code)
 	semfile_list_free(&recycle_semfiles);
 	semfile_list_free(&shutdown_semfiles);
 
+	if (tls_context != -1) {
+		cryptDestroyContext(tls_context);
+		tls_context = -1;
+	}
+
 	update_clients();
 
 #ifdef _WINSOCKAPI_	
@@ -1723,23 +1645,47 @@ const char* DLLCALL services_ver(void)
 	return(ver);
 }
 
+void service_udp_sock_cb(SOCKET sock, void *cbdata)
+{
+	service_t	*serv = (service_t *)cbdata;
+	int				optval;
+
+	open_socket_cb(sock, cbdata);
+
+	/* We need to set the REUSE ADDRESS socket option */
+	optval=TRUE;
+	if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&optval,sizeof(optval))!=0) {
+		lprintf(LOG_ERR,"%04d !ERROR %d setting %s socket option"
+			,sock, ERROR_VALUE, serv->protocol);
+		close_socket(sock);
+		return;
+	}
+   #ifdef BSD
+	if(setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (char*)&optval,sizeof(optval))!=0) {
+		lprintf(LOG_ERR,"%04d !ERROR %d setting %s socket option",sock, ERROR_VALUE, serv->protocol);
+		close_socket(sock);
+		return;
+	}
+   #endif
+}
+
 void DLLCALL services_thread(void* arg)
 {
 	char*			p;
 	char			path[MAX_PATH+1];
 	char			error[256];
-	char			host_ip[32];
+	char			host_ip[64];
 	char			compiler[32];
 	char			str[128];
 	char			services_ini[MAX_PATH+1];
-	SOCKADDR_IN		addr;
-	SOCKADDR_IN		client_addr;
+	union xp_sockaddr	addr;
+	socklen_t		addr_len;
+	union xp_sockaddr	client_addr;
 	socklen_t		client_addr_len;
-	SOCKET			socket;
 	SOCKET			client_socket;
 	BYTE*			udp_buf = NULL;
 	int				udp_len;
-	int				i;
+	int				i,j;
 	int				result;
 	int				optval;
 	ulong			total_running;
@@ -1750,6 +1696,7 @@ void DLLCALL services_thread(void* arg)
 	ulong			total_sockets;
 	struct timeval	tv;
 	service_client_t* client;
+	char			ssl_estr[SSL_ESTR_LEN];
 
 	services_ver();
 
@@ -1865,82 +1812,44 @@ void DLLCALL services_thread(void* arg)
 			return;
 		}
 
+		tls_context = get_ssl_cert(&scfg, ssl_estr);
+		if (tls_context == -1)
+			lprintf(LOG_ERR, "Error creating TLS certificate: %s", ssl_estr);
+
 		update_clients();
 
 		/* Open and Bind Listening Sockets */
 		total_sockets=0;
-		for(i=0;i<(int)services;i++)
-			service[i].socket=INVALID_SOCKET;
 
 		for(i=0;i<(int)services && !startup->shutdown_now;i++) {
+			struct in_addr	iaddr;
 
-			if((socket = open_socket(
-				(service[i].options&SERVICE_OPT_UDP) ? SOCK_DGRAM : SOCK_STREAM
-				,service[i].protocol))
-				==INVALID_SOCKET) {
-				lprintf(LOG_CRIT,"!ERROR %d opening %s socket"
-					,ERROR_VALUE, service[i].protocol);
-				cleanup(1);
-				return;
-			}
-
-			if(service[i].options&SERVICE_OPT_UDP) {
-				/* We need to set the REUSE ADDRESS socket option */
-				optval=TRUE;
-				if(setsockopt(socket,SOL_SOCKET,SO_REUSEADDR
-					,(char*)&optval,sizeof(optval))!=0) {
-					lprintf(LOG_ERR,"%04d !ERROR %d setting %s socket option"
-						,socket, ERROR_VALUE, service[i].protocol);
-					close_socket(socket);
+			if (service[i].options & SERVICE_OPT_TLS) {
+				if (tls_context == -1)
+					continue;
+				if (service[i].options & SERVICE_OPT_UDP) {
+					lprintf(LOG_ERR, "Option error, TLS and UDP specified for %s", service[i].protocol);
 					continue;
 				}
-			   #ifdef BSD
-				if(setsockopt(socket,SOL_SOCKET,SO_REUSEPORT
-					,(char*)&optval,sizeof(optval))!=0) {
-					lprintf(LOG_ERR,"%04d !ERROR %d setting %s socket option"
-						,socket, ERROR_VALUE, service[i].protocol);
-					close_socket(socket);
+				if (service[i].options & SERVICE_OPT_NATIVE) {
+					lprintf(LOG_ERR, "Option error, TLS not yet supported for native services (%s)", service[i].protocol);
 					continue;
 				}
-			   #endif
-			}
-			memset(&addr, 0, sizeof(addr));
-
-			addr.sin_addr.s_addr = htonl(service[i].interface_addr);
-			addr.sin_family = AF_INET;
-			addr.sin_port   = htons(service[i].port);
-
-			if(service[i].port < IPPORT_RESERVED) {
-				if(startup->seteuid!=NULL)
-					startup->seteuid(FALSE);
-			}
-			result=retry_bind(socket, (struct sockaddr *) &addr, sizeof(addr)
-				,startup->bind_retry_count, startup->bind_retry_delay, service[i].protocol, lprintf);
-			if(service[i].port < IPPORT_RESERVED) {
-				if(startup->seteuid!=NULL)
-					startup->seteuid(TRUE);
-			}
-			if(result!=0) {
-				lprintf(LOG_ERR,"%04d %s",socket,BIND_FAILURE_HELP);
-				close_socket(socket);
-				continue;
-			}
-
-			lprintf(LOG_INFO,"%04d %s socket bound to %s port %u"
-				,socket, service[i].protocol
-				,service[i].options&SERVICE_OPT_UDP ? "UDP" : "TCP"
-				,service[i].port);
-
-			if(!(service[i].options&SERVICE_OPT_UDP)) {
-				if(listen(socket,service[i].listen_backlog)!=0) {
-					lprintf(LOG_ERR,"%04d !ERROR %d listening on %s socket"
-						,socket, ERROR_VALUE, service[i].protocol);
-					close_socket(socket);
+				if (service[i].options & SERVICE_OPT_STATIC) {
+					lprintf(LOG_ERR, "Option error, TLS not yet supported for static services (%s)", service[i].protocol);
 					continue;
 				}
 			}
-			service[i].socket=socket;
-			total_sockets++;
+			service[i].set=xpms_create(startup->bind_retry_count, startup->bind_retry_delay, lprintf);
+			if(service[i].set == NULL) {
+				lprintf(LOG_CRIT,"!ERROR creating %s socket set", service[i].protocol);
+				cleanup(1);
+				return;
+			}
+			xpms_add_list(service[i].set, PF_UNSPEC, (service[i].options&SERVICE_OPT_UDP) ? SOCK_DGRAM : SOCK_STREAM
+					, IPPROTO_IP, service[i].interfaces, service[i].port, service[i].protocol
+					, (service[i].options&SERVICE_OPT_UDP) ? service_udp_sock_cb : open_socket_cb, startup->seteuid, &service[i]);
+			total_sockets += service[i].set->sock_count;
 		}
 
 		if(!total_sockets) {
@@ -1953,15 +1862,25 @@ void DLLCALL services_thread(void* arg)
 		for(i=0;i<(int)services;i++) {
 			if(!(service[i].options&SERVICE_OPT_STATIC))
 				continue;
-			if(service[i].socket==INVALID_SOCKET)	/* bind failure? */
+			if(service[i].set==NULL)	/* bind failure? */
 				continue;
 
 			/* start thread here */
-			protected_uint32_adjust(&threads_pending_start, 1);
-			if(service[i].options&SERVICE_OPT_NATIVE)	/* Native */
-				_beginthread(native_static_service_thread, service[i].stack_size, &service[i]);
-			else										/* JavaScript */
+			if(service[i].options&SERVICE_OPT_NATIVE) {	/* Native */
+				for(j=0; j<service[i].set->sock_count; j++) {
+					struct native_service_instance	*inst=(struct native_service_instance *)malloc(sizeof(struct native_service_instance));
+					if(inst) {
+						inst->socket=service[i].set->socks[j].sock;
+						inst->service=&service[i];
+						protected_uint32_adjust(&threads_pending_start, 1);
+						_beginthread(native_static_service_thread, service[i].stack_size, inst);
+					}
+				}
+			}
+			else {										/* JavaScript */
+				protected_uint32_adjust(&threads_pending_start, 1);
 				_beginthread(js_static_service_thread, service[i].stack_size, &service[i]);
+			}
 		}
 
 		status("Listening");
@@ -2016,14 +1935,16 @@ void DLLCALL services_thread(void* arg)
 			for(i=0;i<(int)services;i++) {
 				if(service[i].options&SERVICE_OPT_STATIC)
 					continue;
-				if(service[i].socket==INVALID_SOCKET)
+				if(service[i].set==NULL)
 					continue;
 				if(!(service[i].options&SERVICE_OPT_FULL_ACCEPT)
 					&& service[i].max_clients && service[i].clients >= service[i].max_clients)
 					continue;
-				FD_SET(service[i].socket,&socket_set);
-				if(service[i].socket>high_socket)
-					high_socket=service[i].socket;
+				for(j=0; j<service[i].set->sock_count; j++) {
+					FD_SET(service[i].set->socks[j].sock,&socket_set);
+					if(service[i].set->socks[j].sock>high_socket)
+						high_socket=service[i].set->socks[j].sock;
+				}
 			}
 			if(high_socket==0) {	/* No dynamic services? */
 				YIELD();
@@ -2047,183 +1968,182 @@ void DLLCALL services_thread(void* arg)
 			/* Determine who services this socket */
 			for(i=0;i<(int)services;i++) {
 
-				if(service[i].socket==INVALID_SOCKET)
-					continue;
-
-				if(!FD_ISSET(service[i].socket,&socket_set))
+				if(service[i].set==NULL)
 					continue;
 
-				client_addr_len = sizeof(client_addr);
-
-				udp_len=0;
-
-				if(service[i].options&SERVICE_OPT_UDP) {
-					/* UDP */
-					if((udp_buf = (BYTE*)calloc(1, MAX_UDP_BUF_LEN)) == NULL) {
-						lprintf(LOG_CRIT,"%04d %s !ERROR %d allocating UDP buffer"
-							,service[i].socket, service[i].protocol, errno);
+				for(j=0; j<service[i].set->sock_count; j++) {
+					if(!FD_ISSET(service[i].set->socks[j].sock,&socket_set))
 						continue;
-					}
 
-					udp_len = recvfrom(service[i].socket
-						,udp_buf, MAX_UDP_BUF_LEN, 0 /* flags */
-						,(struct sockaddr *)&client_addr, &client_addr_len);
-					if(udp_len<1) {
-						FREE_AND_NULL(udp_buf);
-						lprintf(LOG_ERR,"%04d %s !ERROR %d recvfrom failed"
-							,service[i].socket, service[i].protocol, ERROR_VALUE);
-						continue;
+					client_addr_len = sizeof(client_addr);
+
+					udp_len=0;
+
+					if(service[i].options&SERVICE_OPT_UDP) {
+						/* UDP */
+						if((udp_buf = (BYTE*)calloc(1, MAX_UDP_BUF_LEN)) == NULL) {
+							lprintf(LOG_CRIT,"%04d %s !ERROR %d allocating UDP buffer"
+								,service[i].set->socks[j].sock, service[i].protocol, errno);
+							continue;
+						}
+
+						udp_len = recvfrom(service[i].set->socks[j].sock
+							,udp_buf, MAX_UDP_BUF_LEN, 0 /* flags */
+							,&client_addr.addr, &client_addr_len);
+						if(udp_len<1) {
+							FREE_AND_NULL(udp_buf);
+							lprintf(LOG_ERR,"%04d %s !ERROR %d recvfrom failed"
+								,service[i].set->socks[j].sock, service[i].protocol, ERROR_VALUE);
+							continue;
+						}
+
+						if((client_socket = open_socket(service[i].set->socks[j].domain, SOCK_DGRAM, &service[i]))
+							==INVALID_SOCKET) {
+							FREE_AND_NULL(udp_buf);
+							lprintf(LOG_ERR,"%04d %s !ERROR %d opening socket"
+								,service[i].set->socks[j].sock, service[i].protocol, ERROR_VALUE);
+							continue;
+						}
+
+						lprintf(LOG_DEBUG,"%04d %s created client socket: %d"
+							,service[i].set->socks[j].sock, service[i].protocol, client_socket);
+
+						/* We need to set the REUSE ADDRESS socket option */
+						optval=TRUE;
+						if(setsockopt(client_socket,SOL_SOCKET,SO_REUSEADDR
+							,(char*)&optval,sizeof(optval))!=0) {
+							FREE_AND_NULL(udp_buf);
+							lprintf(LOG_ERR,"%04d %s !ERROR %d setting socket option"
+								,client_socket, service[i].protocol, ERROR_VALUE);
+							close_socket(client_socket);
+							continue;
+						}
+					   #ifdef BSD
+						if(setsockopt(client_socket,SOL_SOCKET,SO_REUSEPORT
+							,(char*)&optval,sizeof(optval))!=0) {
+							FREE_AND_NULL(udp_buf);
+							lprintf(LOG_ERR,"%04d %s !ERROR %d setting socket option"
+								,client_socket, service[i].protocol, ERROR_VALUE);
+							close_socket(client_socket);
+							continue;
+						}
+					   #endif
+
+						addr_len = sizeof(addr);
+						getsockname(service[i].set->socks[j].sock, &addr.addr, &addr_len);
+						result=bind(client_socket, &addr.addr, addr_len);
+						if(result==SOCKET_ERROR) {
+							/* Failed to re-bind to same port number, use user port */
+							lprintf(LOG_NOTICE,"%04d %s ERROR %d re-binding socket to port %u failed, "
+								"using user port"
+								,client_socket, service[i].protocol, ERROR_VALUE, service[i].port);
+							inet_setaddrport(&addr, 0);
+							result=bind(client_socket, (struct sockaddr *) &addr, addr_len);
+						}
+						if(result!=0) {
+							FREE_AND_NULL(udp_buf);
+							lprintf(LOG_ERR,"%04d %s !ERROR %d re-binding socket to port %u"
+								,client_socket, service[i].protocol, ERROR_VALUE, service[i].port);
+							close_socket(client_socket);
+							continue;
+						}
+
+						/* Set client address as default addres for send/recv */
+						if(connect(client_socket
+							,(struct sockaddr *)&client_addr, client_addr_len)!=0) {
+							FREE_AND_NULL(udp_buf);
+							lprintf(LOG_ERR,"%04d %s !ERROR %d connect failed"
+								,client_socket, service[i].protocol, ERROR_VALUE);
+							close_socket(client_socket);
+							continue;
+						}
+
+					} else { 
+						/* TCP */
+						if((client_socket=accept(service[i].set->socks[j].sock
+							,(struct sockaddr *)&client_addr, &client_addr_len))==INVALID_SOCKET) {
+							if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINVAL)
+								lprintf(LOG_NOTICE,"%04d %s socket closed while listening"
+									,service[i].set->socks[j].sock, service[i].protocol);
+							else
+								lprintf(LOG_WARNING,"%04d %s !ERROR %d accepting connection" 
+									,service[i].set->socks[j].sock, service[i].protocol, ERROR_VALUE);
+	#ifdef _WIN32
+							if(WSAGetLastError()==WSAENOBUFS)	/* recycle (re-init WinSock) on this error */
+								break;
+	#endif
+							continue;
+						}
+						if(startup->socket_open!=NULL)	/* Callback, increments socket counter */
+							startup->socket_open(startup->cbdata,TRUE);	
 					}
+					inet_addrtop(&client_addr, host_ip, sizeof(host_ip));
 
-					if((client_socket = open_socket(SOCK_DGRAM, service[i].protocol))
-						==INVALID_SOCKET) {
+					if(trashcan(&scfg,host_ip,"ip-silent")) {
 						FREE_AND_NULL(udp_buf);
-						lprintf(LOG_ERR,"%04d %s !ERROR %d opening socket"
-							,service[i].socket, service[i].protocol, ERROR_VALUE);
+						close_socket(client_socket);
 						continue;
 					}
 
-					lprintf(LOG_DEBUG,"%04d %s created client socket: %d"
-						,service[i].socket, service[i].protocol, client_socket);
+					lprintf(LOG_INFO,"%04d %s connection accepted from: %s port %u"
+						,client_socket
+						,service[i].protocol, host_ip, inet_addrport(&client_addr));
 
-					/* We need to set the REUSE ADDRESS socket option */
-					optval=TRUE;
-					if(setsockopt(client_socket,SOL_SOCKET,SO_REUSEADDR
-						,(char*)&optval,sizeof(optval))!=0) {
-						FREE_AND_NULL(udp_buf);
-						lprintf(LOG_ERR,"%04d %s !ERROR %d setting socket option"
-							,client_socket, service[i].protocol, ERROR_VALUE);
-						close_socket(client_socket);
-						continue;
-					}
-				   #ifdef BSD
-					if(setsockopt(client_socket,SOL_SOCKET,SO_REUSEPORT
-						,(char*)&optval,sizeof(optval))!=0) {
-						FREE_AND_NULL(udp_buf);
-						lprintf(LOG_ERR,"%04d %s !ERROR %d setting socket option"
-							,client_socket, service[i].protocol, ERROR_VALUE);
+					if(service[i].max_clients && service[i].clients+1>service[i].max_clients) {
+						lprintf(LOG_WARNING,"%04d !%s MAXIMUM CLIENTS (%u) reached, access denied"
+							,client_socket, service[i].protocol, service[i].max_clients);
+						mswait(3000);
 						close_socket(client_socket);
 						continue;
 					}
-				   #endif
-
-					memset(&addr, 0, sizeof(addr));
-					addr.sin_addr.s_addr = htonl(service[i].interface_addr);
-					addr.sin_family = AF_INET;
-					addr.sin_port   = htons(service[i].port);
-
-					result=bind(client_socket, (struct sockaddr *) &addr, sizeof(addr));
-					if(result==SOCKET_ERROR) {
-						/* Failed to re-bind to same port number, use user port */
-						lprintf(LOG_NOTICE,"%04d %s ERROR %d re-binding socket to port %u failed, "
-							"using user port"
-							,client_socket, service[i].protocol, ERROR_VALUE, service[i].port);
-						addr.sin_port=0;
-						result=bind(client_socket, (struct sockaddr *) &addr, sizeof(addr));
-					}
-					if(result!=0) {
+
+	#ifdef _WIN32
+					if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)
+						&& !(service[i].options&BBS_OPT_MUTE))
+						PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME);
+	#endif
+
+					if(trashcan(&scfg,host_ip,"ip")) {
 						FREE_AND_NULL(udp_buf);
-						lprintf(LOG_ERR,"%04d %s !ERROR %d re-binding socket to port %u"
-							,client_socket, service[i].protocol, ERROR_VALUE, service[i].port);
+						lprintf(LOG_NOTICE,"%04d !%s CLIENT BLOCKED in ip.can: %s"
+							,client_socket, service[i].protocol, host_ip);
+						mswait(3000);
 						close_socket(client_socket);
 						continue;
 					}
 
-					/* Set client address as default addres for send/recv */
-					if(connect(client_socket
-						,(struct sockaddr *)&client_addr, client_addr_len)!=0) {
+					if((client=malloc(sizeof(service_client_t)))==NULL) {
 						FREE_AND_NULL(udp_buf);
-						lprintf(LOG_ERR,"%04d %s !ERROR %d connect failed"
-							,client_socket, service[i].protocol, ERROR_VALUE);
+						lprintf(LOG_CRIT,"%04d !%s ERROR allocating %u bytes of memory for service_client"
+							,client_socket, service[i].protocol, sizeof(service_client_t));
+						mswait(3000);
 						close_socket(client_socket);
 						continue;
 					}
 
-				} else { 
-					/* TCP */
-					if((client_socket=accept(service[i].socket
-						,(struct sockaddr *)&client_addr, &client_addr_len))==INVALID_SOCKET) {
-						if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINVAL)
-            				lprintf(LOG_NOTICE,"%04d %s socket closed while listening"
-								,service[i].socket, service[i].protocol);
-						else
-							lprintf(LOG_WARNING,"%04d %s !ERROR %d accepting connection" 
-								,service[i].socket, service[i].protocol, ERROR_VALUE);
-#ifdef _WIN32
-						if(WSAGetLastError()==WSAENOBUFS)	/* recycle (re-init WinSock) on this error */
-							break;
-#endif
-						continue;
-					}
-					if(startup->socket_open!=NULL)	/* Callback, increments socket counter */
-						startup->socket_open(startup->cbdata,TRUE);	
-				}
-				SAFECOPY(host_ip,inet_ntoa(client_addr.sin_addr));
-
-				if(trashcan(&scfg,host_ip,"ip-silent")) {
-					FREE_AND_NULL(udp_buf);
-					close_socket(client_socket);
-					continue;
-				}
-
-				lprintf(LOG_INFO,"%04d %s connection accepted from: %s port %u"
-					,client_socket
-					,service[i].protocol, host_ip, ntohs(client_addr.sin_port));
-
-				if(service[i].max_clients && service[i].clients+1>service[i].max_clients) {
-					lprintf(LOG_WARNING,"%04d !%s MAXIMUM CLIENTS (%u) reached, access denied"
-						,client_socket, service[i].protocol, service[i].max_clients);
-					mswait(3000);
-					close_socket(client_socket);
-					continue;
-				}
-
-#ifdef _WIN32
-				if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)
-					&& !(service[i].options&BBS_OPT_MUTE))
-					PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME);
-#endif
-
-				if(trashcan(&scfg,host_ip,"ip")) {
-					FREE_AND_NULL(udp_buf);
-					lprintf(LOG_NOTICE,"%04d !%s CLIENT BLOCKED in ip.can: %s"
-						,client_socket, service[i].protocol, host_ip);
-					mswait(3000);
-					close_socket(client_socket);
-					continue;
+					memset(client,0,sizeof(service_client_t));
+					client->socket=client_socket;
+					client->addr=client_addr;
+					client->service=&service[i];
+					client->service->clients++;		/* this should be mutually exclusive */
+					client->udp_buf=udp_buf;
+					client->udp_len=udp_len;
+					client->callback.limit			= service[i].js.time_limit;
+					client->callback.gc_interval	= service[i].js.gc_interval;
+					client->callback.yield_interval	= service[i].js.yield_interval;
+					client->callback.terminated		= &client->service->terminated;
+					client->callback.auto_terminate	= TRUE;
+
+					udp_buf = NULL;
+
+					protected_uint32_adjust(&threads_pending_start, 1);
+					if(service[i].options&SERVICE_OPT_NATIVE)	/* Native */
+						_beginthread(native_service_thread, service[i].stack_size, client);
+					else										/* JavaScript */
+						_beginthread(js_service_thread, service[i].stack_size, client);
+					service[i].served++;
+					served++;
 				}
-
-				if((client=malloc(sizeof(service_client_t)))==NULL) {
-					FREE_AND_NULL(udp_buf);
-					lprintf(LOG_CRIT,"%04d !%s ERROR allocating %u bytes of memory for service_client"
-						,client_socket, service[i].protocol, sizeof(service_client_t));
-					mswait(3000);
-					close_socket(client_socket);
-					continue;
-				}
-
-				memset(client,0,sizeof(service_client_t));
-				client->socket=client_socket;
-				client->addr=client_addr;
-				client->service=&service[i];
-				client->service->clients++;		/* this should be mutually exclusive */
-				client->udp_buf=udp_buf;
-				client->udp_len=udp_len;
-				client->callback.limit			= service[i].js.time_limit;
-				client->callback.gc_interval	= service[i].js.gc_interval;
-				client->callback.yield_interval	= service[i].js.yield_interval;
-				client->callback.terminated		= &client->service->terminated;
-				client->callback.auto_terminate	= TRUE;
-
-				udp_buf = NULL;
-
-				protected_uint32_adjust(&threads_pending_start, 1);
-				if(service[i].options&SERVICE_OPT_NATIVE)	/* Native */
-					_beginthread(native_service_thread, service[i].stack_size, client);
-				else										/* JavaScript */
-					_beginthread(js_service_thread, service[i].stack_size, client);
-				service[i].served++;
-				served++;
 			}
 		}
 
@@ -2231,12 +2151,12 @@ void DLLCALL services_thread(void* arg)
 		lprintf(LOG_DEBUG,"0000 Closing service sockets");
 		for(i=0;i<(int)services;i++) {
 			service[i].terminated=TRUE;
-			if(service[i].socket==INVALID_SOCKET)
+			if(service[i].set==NULL)
 				continue;
 			if(service[i].options&SERVICE_OPT_STATIC)
 				continue;
-			close_socket(service[i].socket);
-			service[i].socket=INVALID_SOCKET;
+			xpms_destroy(service[i].set, close_socket_cb, &service[i]);
+			service[i].set=NULL;
 		}
 
 		/* Wait for Dynamic Service Threads to terminate */
diff --git a/src/sbbs3/services.h b/src/sbbs3/services.h
index 317234f2c0b19d05851250507aca783841e5c499..642073f62c311fd8c1fea72d69150bc3d30ee73d 100644
--- a/src/sbbs3/services.h
+++ b/src/sbbs3/services.h
@@ -43,7 +43,9 @@
 typedef struct {
 
 	DWORD	size;				/* sizeof(bbs_struct_t) */
-    DWORD   interface_addr;
+	struct in_addr outgoing4;
+	struct in6_addr	outgoing6;
+	str_list_t		interfaces;
     DWORD	options;			/* See BBS_OPT definitions */
 	WORD	sem_chk_freq;			/* semaphore file checking frequency (in seconds) */
 
@@ -92,7 +94,8 @@ typedef struct {
 #if 0
 /* startup options that requires re-initialization/recycle when changed */
 static struct init_field services_init_fields[] = { 
-	 OFFSET_AND_SIZE(services_startup_t,interface_addr)
+	 OFFSET_AND_SIZE(services_startup_t,outgoing4)
+	 OFFSET_AND_SIZE(services_startup_t,outgoing6)
 	,OFFSET_AND_SIZE(services_startup_t,ctrl_dir)
 	,{ 0,0 }	/* terminator */
 };
@@ -104,6 +107,7 @@ static struct init_field services_init_fields[] = {
 #define SERVICE_OPT_STATIC_LOOP (1<<2)	/* Loop static service until terminated */
 #define SERVICE_OPT_NATIVE		(1<<3)	/* non-JavaScript service */
 #define SERVICE_OPT_FULL_ACCEPT	(1<<4)	/* Accept/close connections when server is full */
+#define SERVICE_OPT_TLS			(1<<5)	/* Use TLS */
 
 /* services_startup_t.options bits that require re-init/recycle when changed */
 #define SERVICE_INIT_OPTS	(0)
@@ -120,6 +124,7 @@ static ini_bitdesc_t service_options[] = {
 	{ SERVICE_OPT_STATIC_LOOP		,"LOOP"					},
 	{ SERVICE_OPT_NATIVE			,"NATIVE"				},
 	{ SERVICE_OPT_FULL_ACCEPT		,"FULL_ACCEPT"			},
+	{ SERVICE_OPT_TLS				,"TLS"					},
 	/* terminator */				
 	{ 0 							,NULL					}
 };
diff --git a/src/sbbs3/sockopts.c b/src/sbbs3/sockopts.c
index 91e1aad35164fb339629ebf712e4511d4eb02e4e..5a484d10903878d1f6f8b6411118a17e3332abe6 100644
--- a/src/sbbs3/sockopts.c
+++ b/src/sbbs3/sockopts.c
@@ -42,7 +42,7 @@ int DLLCALL set_socket_options(scfg_t* cfg, SOCKET sock, const char* protocol, c
 {
 	char		cfgfile[MAX_PATH+1];
 	FILE*		fp;
-	int			type;
+	int			type=0;		// Assignment is to silence Valgrind
 	int			result=0;
 	str_list_t	list;
 	socklen_t	len;
diff --git a/src/sbbs3/ssl.c b/src/sbbs3/ssl.c
index 40987e5575897ff31a5fb39c360ab121ec6ff712..ff78893597f6a2356abc5d592b07c0721c39cc88 100644
--- a/src/sbbs3/ssl.c
+++ b/src/sbbs3/ssl.c
@@ -25,7 +25,7 @@ static bool get_error_string(int status, CRYPT_SESSION sess, char *estr, char *f
 
 #define DO(x)	get_error_string(x, ssl_context, estr, __FILE__, __LINE__)
 
-CRYPT_CONTEXT get_ssl_cert(scfg_t *cfg, char *estr)
+CRYPT_CONTEXT DLLCALL get_ssl_cert(scfg_t *cfg, char *estr)
 {
 	CRYPT_KEYSET		ssl_keyset;
 	CRYPT_CONTEXT		ssl_context;
diff --git a/src/sbbs3/ssl.h b/src/sbbs3/ssl.h
index 1b4a9bfd81be1f5ee4f9b2f8a677b3d2d5bc6c84..820c3b5b1b1cd307e3213a6b69e827350c4809ea 100644
--- a/src/sbbs3/ssl.h
+++ b/src/sbbs3/ssl.h
@@ -1,11 +1,12 @@
 #ifndef SBBS_SSL_H
 #define SBBS_SSL_H
 
+#include "sbbs.h"	// For DLLEXPORT
 #include <cryptlib.h>
 #include "scfgdefs.h"
 
 #define SSL_ESTR_LEN	CRYPT_MAX_TEXTSIZE + 1024 /* File name, line number, status code, and some static text */
 
-CRYPT_CONTEXT get_ssl_cert(scfg_t *cfg, char *estr);
+DLLEXPORT CRYPT_CONTEXT DLLCALL get_ssl_cert(scfg_t *cfg, char *estr);
 
 #endif
diff --git a/src/sbbs3/startup.h b/src/sbbs3/startup.h
index 8e821e9ed6962fcb2ed40ca6017f55f4c016e920..5be96476f241f07f17a397baa3d4fdabab59205c 100644
--- a/src/sbbs3/startup.h
+++ b/src/sbbs3/startup.h
@@ -64,7 +64,9 @@ typedef struct {
 	char	temp_dir[INI_MAX_VALUE_LEN];
 	char	host_name[INI_MAX_VALUE_LEN];
 	ushort	sem_chk_freq;
-	ulong	interface_addr;
+	struct in_addr		outgoing4;
+	struct in6_addr	outgoing6;
+	str_list_t		interfaces;
 	int		log_level;
 	js_startup_t js;
 	uint	bind_retry_count;		/* Number of times to retry bind() calls */
@@ -87,10 +89,12 @@ typedef struct {
 	WORD	outbuf_highwater_mark;	/* output block size control */
 	WORD	outbuf_drain_timeout;
 	WORD	sem_chk_freq;		/* semaphore file checking frequency (in seconds) */
-    uint32_t   telnet_interface;
+	struct in_addr outgoing4;
+	struct in6_addr	outgoing6;
+    str_list_t	telnet_interfaces;
     uint32_t	options;			/* See BBS_OPT definitions */
-    DWORD	rlogin_interface;
-	DWORD	ssh_interface;
+    str_list_t	rlogin_interfaces;
+    str_list_t	ssh_interfaces;
     RingBuf** node_spybuf;			/* Spy output buffer (each node)	*/
     RingBuf** node_inbuf;			/* User input buffer (each node)	*/
     sem_t**	node_spysem;			/* Spy output semaphore (each node)	*/
@@ -154,8 +158,9 @@ static struct init_field {
 	,OFFSET_AND_SIZE(bbs_startup_t,last_node)
 	,OFFSET_AND_SIZE(bbs_startup_t,telnet_port)
 	,OFFSET_AND_SIZE(bbs_startup_t,rlogin_port)
-	,OFFSET_AND_SIZE(bbs_startup_t,telnet_interface)
-	,OFFSET_AND_SIZE(bbs_startup_t,rlogin_interface)
+	,OFFSET_AND_SIZE(bbs_startup_t,telnet_interfaces)
+	,OFFSET_AND_SIZE(bbs_startup_t,rlogin_interfaces)
+	,OFFSET_AND_SIZE(bbs_startup_t,ssh_interfaces)
 	,OFFSET_AND_SIZE(bbs_startup_t,ctrl_dir)
 	,OFFSET_AND_SIZE(bbs_startup_t,temp_dir)
 	,{ 0,0 }	/* terminator */
diff --git a/src/sbbs3/str.cpp b/src/sbbs3/str.cpp
index 59d7953bc339fa7c39de7b37a372f3630c4f856e..666c3be7406d3c57041ba1a39a25a0aa2bb0239e 100644
--- a/src/sbbs3/str.cpp
+++ b/src/sbbs3/str.cpp
@@ -95,14 +95,14 @@ void sbbs_t::userlist(long mode)
 			}
 			sprintf(name,"%s #%d",user.alias,i);
 			sprintf(line[j],text[UserListFmt],name
-				,cfg.sys_misc&SM_LISTLOC ? user.location : user.note
+				,cfg.sys_misc&SM_LISTLOC ? user.location : user.ipaddr
 				,unixtodstr(&cfg,user.laston,tmp)
 				,user.modem); 
 		}
 		else {
 			sprintf(name,"%s #%u",user.alias,i);
 			bprintf(text[UserListFmt],name
-				,cfg.sys_misc&SM_LISTLOC ? user.location : user.note
+				,cfg.sys_misc&SM_LISTLOC ? user.location : user.ipaddr
 				,unixtodstr(&cfg,user.laston,tmp)
 				,user.modem); 
 		}
diff --git a/src/sbbs3/telgate.cpp b/src/sbbs3/telgate.cpp
index 5cfa402f3b90e491884ea0346675c438302fe1d6..ed606b46de4836b7febd60e8f7f8fb1a79e64f91 100644
--- a/src/sbbs3/telgate.cpp
+++ b/src/sbbs3/telgate.cpp
@@ -77,7 +77,7 @@ void sbbs_t::telnet_gate(char* destaddr, ulong mode, char* client_user_name, cha
 	}
 
 	memset(&addr,0,sizeof(addr));
-	addr.sin_addr.s_addr = htonl(startup->telnet_interface);
+	addr.sin_addr.s_addr = htonl(startup->outgoing4.s_addr);
 	addr.sin_family = AF_INET;
 
 	if((i=bind(remote_socket, (struct sockaddr *) &addr, sizeof (addr)))!=0) {
diff --git a/src/sbbs3/userdat.c b/src/sbbs3/userdat.c
index 659ab360bea2fb944840c82a79a6fa7a680a086f..00317c00f1b1c938f5fdf383013432aa87f4d89d 100644
--- a/src/sbbs3/userdat.c
+++ b/src/sbbs3/userdat.c
@@ -263,6 +263,7 @@ int DLLCALL getuserdat(scfg_t* cfg, user_t *user)
 	getrec(userdat,U_PHONE,LEN_PHONE,user->phone);
 	getrec(userdat,U_BIRTH,LEN_BIRTH,user->birth);
 	getrec(userdat,U_MODEM,LEN_MODEM,user->modem);
+	getrec(userdat,U_IPADDR,LEN_IPADDR,user->ipaddr);
 	getrec(userdat,U_LASTON,8,str); user->laston=ahtoul(str);
 	getrec(userdat,U_FIRSTON,8,str); user->firston=ahtoul(str);
 	getrec(userdat,U_EXPIRE,8,str); user->expire=ahtoul(str);
@@ -433,6 +434,7 @@ int DLLCALL putuserdat(scfg_t* cfg, user_t* user)
 	putrec(userdat,U_PHONE,LEN_PHONE,user->phone);
 	putrec(userdat,U_BIRTH,LEN_BIRTH,user->birth);
 	putrec(userdat,U_MODEM,LEN_MODEM,user->modem);
+	putrec(userdat,U_IPADDR,LEN_IPADDR,user->modem);
 	putrec(userdat,U_LASTON,8,ultoa((ulong)user->laston,str,16));
 	putrec(userdat,U_FIRSTON,8,ultoa((ulong)user->firston,str,16));
 	putrec(userdat,U_EXPIRE,8,ultoa((ulong)user->expire,str,16));
@@ -481,7 +483,7 @@ int DLLCALL putuserdat(scfg_t* cfg, user_t* user)
 
 	putrec(userdat,U_XFER_CMD+LEN_XFER_CMD,2,crlf);
 
-	putrec(userdat,U_MAIL_CMD+LEN_MAIL_CMD,2,crlf);
+	putrec(userdat,U_IPADDR+LEN_IPADDR,2,crlf);
 
 	putrec(userdat,U_FREECDT,10,ultoa(user->freecdt,str,10));
 
@@ -1835,7 +1837,7 @@ static BOOL ar_exp(scfg_t* cfg, uchar **ptrptr, user_t* user, client_t* client)
 				if(client!=NULL)
 					p=client->addr;
 				else if(user!=NULL)
-					p=user->note;
+					p=user->ipaddr;
 				else
 					p=NULL;
 				if(!findstr_in_string(p,(char*)*ptrptr))
@@ -2407,6 +2409,7 @@ int DLLCALL user_rec_len(int offset)
 		case U_PHONE:		return(LEN_PHONE);
 		case U_BIRTH:		return(LEN_BIRTH);
 		case U_MODEM:		return(LEN_MODEM);
+		case U_IPADDR:		return(LEN_IPADDR);
 
 		/* Internal codes (16 chars) */
 		case U_CURSUB:
@@ -2734,27 +2737,39 @@ long DLLCALL loginAttemptListClear(link_list_t* list)
 }
 
 /****************************************************************************/
-static list_node_t* login_attempted(link_list_t* list, const SOCKADDR_IN* addr)
+static list_node_t* login_attempted(link_list_t* list, const union xp_sockaddr* addr)
 {
 	list_node_t*		node;
 	login_attempt_t*	attempt;
+	struct in6_addr		ia;
 
 	if(list==NULL)
 		return NULL;
 	for(node=list->first; node!=NULL; node=node->next) {
 		attempt=node->data;
-		if(memcmp(&attempt->addr,&addr->sin_addr,sizeof(attempt->addr))==0)
+		switch(addr->addr.sa_family) {
+			case AF_INET:
+				memset(&ia, 0, sizeof(ia));
+				memcpy(&ia, &addr->in.sin_addr, sizeof(addr->in.sin_addr));
+				break;
+			case AF_INET6:
+				ia = addr->in6.sin6_addr;
+				break;
+		}
+		if(memcmp(&attempt->addr,&ia,sizeof(attempt->addr))==0)
 			break;
 	}
 	return node;
 }
 
 /****************************************************************************/
-long DLLCALL loginAttempts(link_list_t* list, const SOCKADDR_IN* addr)
+long DLLCALL loginAttempts(link_list_t* list, const union xp_sockaddr* addr)
 {
 	long				count=0;
 	list_node_t*		node;
 
+	if(addr->addr.sa_family != AF_INET && addr->addr.sa_family != AF_INET6)
+		return 0;
 	listLock(list);
 	if((node=login_attempted(list, addr))!=NULL)
 		count = ((login_attempt_t*)node->data)->count - ((login_attempt_t*)node->data)->dupes;
@@ -2764,10 +2779,12 @@ long DLLCALL loginAttempts(link_list_t* list, const SOCKADDR_IN* addr)
 }
 
 /****************************************************************************/
-void DLLCALL loginSuccess(link_list_t* list, const SOCKADDR_IN* addr)
+void DLLCALL loginSuccess(link_list_t* list, const union xp_sockaddr* addr)
 {
 	list_node_t*		node;
 
+	if(addr->addr.sa_family != AF_INET && addr->addr.sa_family != AF_INET6)
+		return;
 	listLock(list);
 	if((node=login_attempted(list, addr)) != NULL)
 		listRemoveNode(list, node, /* freeData: */TRUE);
@@ -2777,13 +2794,15 @@ void DLLCALL loginSuccess(link_list_t* list, const SOCKADDR_IN* addr)
 /****************************************************************************/
 /* Returns number of *unique* login attempts (excludes consecutive dupes)	*/
 /****************************************************************************/
-ulong DLLCALL loginFailure(link_list_t* list, const SOCKADDR_IN* addr, const char* prot, const char* user, const char* pass)
+ulong DLLCALL loginFailure(link_list_t* list, const union xp_sockaddr* addr, const char* prot, const char* user, const char* pass)
 {
 	list_node_t*		node;
 	login_attempt_t		first;
 	login_attempt_t*	attempt=&first;
 	ulong				count=0;
 
+	if(addr->addr.sa_family != AF_INET && addr->addr.sa_family != AF_INET6)
+		return 0;
 	if(list==NULL)
 		return 0;
 	memset(&first, 0, sizeof(first));
@@ -2796,7 +2815,16 @@ ulong DLLCALL loginFailure(link_list_t* list, const SOCKADDR_IN* addr, const cha
 	}
 	SAFECOPY(attempt->prot,prot);
 	attempt->time=time32(NULL);
-	attempt->addr=addr->sin_addr;
+	memset(&attempt->addr, 0, sizeof(attempt->addr));
+	attempt->family = addr->addr.sa_family;
+	switch(addr->addr.sa_family) {
+		case AF_INET:
+			memcpy(&attempt->addr, &addr->in.sin_addr, sizeof(addr->in.sin_addr));
+			break;
+		case AF_INET6:
+			memcpy(&attempt->addr, &addr->in6.sin6_addr, sizeof(addr->in6.sin6_addr));
+			break;
+	}
 	SAFECOPY(attempt->user, user);
 	SAFECOPY(attempt->pass, pass);
 	attempt->count++;
diff --git a/src/sbbs3/userdat.h b/src/sbbs3/userdat.h
index d90fd1ddb5c7b41bb7391cdf9c65185e7519245a..846afda9ed9e66b374ac7090fef25d9a7ea6c7d6 100644
--- a/src/sbbs3/userdat.h
+++ b/src/sbbs3/userdat.h
@@ -135,7 +135,8 @@ DLLEXPORT BOOL	DLLCALL check_name(scfg_t* cfg, const char* name);
 
 /* Login attempt/hack tracking */
 typedef struct {
-	IN_ADDR		addr;	/* host with consecutive failed login attmepts */
+	struct in6_addr addr;	/* host with consecutive failed login attmepts */
+	sa_family_t	family;
 	ulong		count;	/* number of consecutive failed login attempts */
 	ulong		dupes;	/* number of consecutive dupliate login attempts (same name and password) */
 	time32_t	time;	/* time of last attempt */
@@ -147,9 +148,9 @@ typedef struct {
 DLLEXPORT link_list_t*		DLLCALL	loginAttemptListInit(link_list_t*);
 DLLEXPORT BOOL				DLLCALL	loginAttemptListFree(link_list_t*);
 DLLEXPORT long				DLLCALL	loginAttemptListClear(link_list_t*);
-DLLEXPORT long				DLLCALL loginAttempts(link_list_t*, const SOCKADDR_IN*);
-DLLEXPORT void				DLLCALL	loginSuccess(link_list_t*, const SOCKADDR_IN*);
-DLLEXPORT ulong				DLLCALL loginFailure(link_list_t*, const SOCKADDR_IN*, const char* prot, const char* user, const char* pass);
+DLLEXPORT long				DLLCALL loginAttempts(link_list_t*, const union xp_sockaddr*);
+DLLEXPORT void				DLLCALL	loginSuccess(link_list_t*, const union xp_sockaddr*);
+DLLEXPORT ulong				DLLCALL loginFailure(link_list_t*, const union xp_sockaddr*, const char* prot, const char* user, const char* pass);
 
 #ifdef __cplusplus
 }
diff --git a/src/sbbs3/useredit/MainFormUnit.pas b/src/sbbs3/useredit/MainFormUnit.pas
index 3b834e655e6289956ed1ad13d480a406156752c6..a8a92e925ec21a8c341d2f6f234e086beb079ccd 100644
--- a/src/sbbs3/useredit/MainFormUnit.pas
+++ b/src/sbbs3/useredit/MainFormUnit.pas
@@ -266,8 +266,8 @@ const					    { String lengths					   	    }
     LEN_TITLE		=70;	{ Message title							    }
     LEN_MAIN_CMD	=40;	{ Storage in user.dat for custom commands	}
     LEN_XFER_CMD	=40;
-    LEN_SCAN_CMD	=40;
-    LEN_MAIL_CMD	=40;
+    LEN_SCAN_CMD	=35;
+    LEN_IPADDR		=45;
     LEN_CID 		=25;	{ Caller ID (phone number) 				    }
     LEN_ARSTR		=40;	{ Max length of Access Requirement string	}
     LEN_CHATACTCMD	=9;	    { Chat action command				   	    }
@@ -335,8 +335,8 @@ const
     U_MAIN_CMD	=U_CMDSET+2+2; 	{ unused }
     U_XFER_CMD	=U_MAIN_CMD+LEN_MAIN_CMD; 		{ unused }
     U_SCAN_CMD	=U_XFER_CMD+LEN_XFER_CMD+2;  	{ unused }
-    U_MAIL_CMD	=U_SCAN_CMD+LEN_SCAN_CMD; 		{ unused }
-    U_FREECDT	=U_MAIL_CMD+LEN_MAIL_CMD+2;
+    U_IPADDR	=U_SCAN_CMD+LEN_SCAN_CMD; 		{ unused }
+    U_FREECDT	=U_IPADDR+LEN_IPADDR+2;
     U_FLAGS3	=U_FREECDT+10; 	{ Flag set #3 }
     U_FLAGS4	=U_FLAGS3+8;	{ Flag set #4 }
     U_XEDIT 	=U_FLAGS4+8; 	{ External editor (code)  }
diff --git a/src/sbbs3/v4upgrade.c b/src/sbbs3/v4upgrade.c
index b39f3cd650a4e9c0d8e221c7bc9b5ea6466f7dde..827a7449524ee02185847e435f18db743a91dd84 100644
--- a/src/sbbs3/v4upgrade.c
+++ b/src/sbbs3/v4upgrade.c
@@ -136,11 +136,12 @@ BOOL upgrade_users(void)
 		}
 		/******************************************/
 		/* personal info */
-		len=sprintf(rec,"%s\t%s\t%s\t%s\t%s\t%s\t"
+		len=sprintf(rec,"%s\t%s\t%s\t%s\t%s\t%s\t%s\t"
 			,user.alias
 			,user.name
 			,user.handle
 			,user.note
+			,user.ipaddr
 			,user.comp
 			,user.comment
 			);
diff --git a/src/sbbs3/websrvr.c b/src/sbbs3/websrvr.c
index 7de6ff0c738854884647313b19e3d7440a64ff09..a8f4a146ba7aa642c7624dfb34c4428921f77612 100644
--- a/src/sbbs3/websrvr.c
+++ b/src/sbbs3/websrvr.c
@@ -48,8 +48,6 @@
  * 
  */
 
-//#define ONE_JS_RUNTIME
-
 /* Headers for CGI stuff */
 #if defined(__unix__)
 	#include <sys/wait.h>		/* waitpid() */
@@ -65,6 +63,7 @@
 #include "sbbs.h"
 #include "sbbsdefs.h"
 #include "sockwrap.h"		/* sendfilesocket() */
+#include "multisock.h"
 #include "threadwrap.h"
 #include "semwrap.h"
 #include "websrvr.h"
@@ -72,8 +71,10 @@
 #include "md5.h"
 #include "js_rtpool.h"
 #include "js_request.h"
+#include "js_socket.h"
 #include "xpmap.h"
 #include "xpprintf.h"
+#include "ssl.h"
 
 static const char*	server_name="Synchronet Web Server";
 static const char*	newline="\r\n";
@@ -84,7 +85,9 @@ static const char*	error_302="302 Moved Temporarily";
 static const char*	error_404="404 Not Found";
 static const char*	error_416="416 Requested Range Not Satisfiable";
 static const char*	error_500="500 Internal Server Error";
+static const char*	error_503="503 Service Unavailable\r\nConnection: close\r\nContent-Length: 0\r\n\r\n";
 static const char*	unknown="<unknown>";
+static int len_503 = 0;
 
 #define TIMEOUT_THREAD_WAIT		60		/* Seconds */
 #define MAX_REQUEST_LINE		1024	/* NOT including terminator */
@@ -106,8 +109,9 @@ static protected_uint32_t active_clients;
 static protected_uint32_t thread_count;
 static volatile ulong	sockets=0;
 static volatile BOOL	terminate_server=FALSE;
+static volatile BOOL	terminated=FALSE;
 static volatile BOOL	terminate_http_logging_thread=FALSE;
-static SOCKET	server_socket=INVALID_SOCKET;
+static struct xpms_set	*ws_set=NULL;
 static char		revision[16];
 static char		root_dir[MAX_PATH+1];
 static char		error_dir[MAX_PATH+1];
@@ -146,12 +150,14 @@ enum auth_type {
 	 AUTHENTICATION_UNKNOWN
 	,AUTHENTICATION_BASIC
 	,AUTHENTICATION_DIGEST
+	,AUTHENTICATION_TLS_PSK
 };
 
-char *auth_type_names[4] = {
+char *auth_type_names[] = {
 	 "Unknown"
 	,"Basic"
 	,"Digest"
+	,"TLS-PSK"
 	,NULL
 };
 
@@ -236,11 +242,10 @@ typedef struct  {
 
 typedef struct  {
 	SOCKET			socket;
-	SOCKADDR_IN		addr;
-	SOCKET			socket6;
-	SOCKADDR_IN		addr6;
+	union xp_sockaddr	addr;
+	socklen_t		addr_len;
 	http_request_t	req;
-	char			host_ip[64];
+	char			host_ip[INET6_ADDRSTRLEN];
 	char			host_name[128];	/* Resolved remote host */
 	int				http_ver;       /* HTTP version.  0 = HTTP/0.9, 1=HTTP/1.0, 2=HTTP/1.1 */
 	BOOL			finished;		/* Do not accept any more imput from client */
@@ -272,9 +277,18 @@ typedef struct  {
 
 	/* Synchronization stuff */
 	pthread_mutex_t	struct_filled;
+
+	/* TLS Stuff */
+	BOOL			is_tls;
+	CRYPT_SESSION	tls_sess;
+	BOOL			tls_pending;
+	BOOL			peeked_valid;
+	char			peeked;
 } http_session_t;
 
-enum { 
+static CRYPT_CONTEXT tls_context = -1;
+
+enum {
 	 HTTP_0_9
 	,HTTP_1_0
 	,HTTP_1_1
@@ -517,9 +531,10 @@ static int writebuf(http_session_t	*session, const char *buf, size_t len)
 	size_t	avail;
 
 	while(sent < len) {
+		ResetEvent(session->outbuf.empty_event);
 		avail=RingBufFree(&session->outbuf);
 		if(!avail) {
-			SLEEP(1);
+			WaitForEvent(session->outbuf.empty_event, 1);
 			continue;
 		}
 		if(avail > len-sent)
@@ -529,47 +544,114 @@ static int writebuf(http_session_t	*session, const char *buf, size_t len)
 	return(sent);
 }
 
-static int sock_sendbuf(SOCKET *sock, const char *buf, size_t len, BOOL *failed)
+#define HANDLE_CRYPT_CALL(status, session)  handle_crypt_call(status, session, __FILE__, __LINE__)
+
+static BOOL handle_crypt_call(int status, http_session_t *session, const char *file, int line)
+{
+	int		len = 0;
+	char	estr[CRYPT_MAX_TEXTSIZE+1];
+	int		sock = 0;
+
+	if (status == CRYPT_OK)
+		return TRUE;
+	if (session != NULL) {
+		if (session->is_tls)
+			cryptGetAttributeString(session->tls_sess, CRYPT_ATTRIBUTE_ERRORMESSAGE, estr, &len);
+		sock = session->socket;
+	}
+	estr[len]=0;
+	if (len)
+		lprintf(LOG_ERR, "%04d cryptlib error %d at %s:%d (%s)", sock, status, file, line, estr);
+	else
+		lprintf(LOG_ERR, "%04d cryptlib error %d at %s:%d", sock, status, file, line);
+	return FALSE;
+}
+
+static BOOL session_check(http_session_t *session, BOOL *rd, BOOL *wr, unsigned timeout)
+{
+	BOOL	ret = FALSE;
+	BOOL	lcl_rd;
+	BOOL	*rd_ptr = rd?rd:&lcl_rd;
+
+	if (session->is_tls) {
+		if(wr)
+			*wr=1;
+		if(rd) {
+			if(session->tls_pending) {
+				*rd = TRUE;
+				return TRUE;
+			}
+		}
+		ret = socket_check(session->socket, rd_ptr, wr, timeout);
+		if (ret && *rd_ptr) {
+			session->tls_pending = TRUE;
+			return TRUE;
+		}
+		return ret;
+	}
+	return socket_check(session->socket, rd, wr, timeout);
+}
+
+static int sess_sendbuf(http_session_t *session, const char *buf, size_t len, BOOL *failed)
 {
 	size_t sent=0;
+	int	tls_sent;
 	int result;
 	int sel;
 	fd_set	wr_set;
 	struct timeval tv;
+	int status;
 
-	while(sent<len && *sock!=INVALID_SOCKET) {
+	while(sent<len && session->socket!=INVALID_SOCKET) {
 		FD_ZERO(&wr_set);
-		FD_SET(*sock,&wr_set);
+		FD_SET(session->socket,&wr_set);
 		/* Convert timeout from ms to sec/usec */
 		tv.tv_sec=startup->max_inactivity;
 		tv.tv_usec=0;
-		sel=select(*sock+1,NULL,&wr_set,NULL,&tv);
+		sel=select(session->socket+1,NULL,&wr_set,NULL,&tv);
 		switch(sel) {
 			case 1:
-				result=sendsocket(*sock,buf+sent,len-sent);
-				if(result==SOCKET_ERROR) {
-					if(ERROR_VALUE==ECONNRESET) 
-						lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",*sock);
-					else if(ERROR_VALUE==ECONNABORTED) 
-						lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",*sock);
+				if (session->is_tls) {
+					status = cryptPushData(session->tls_sess, buf+sent, len-sent, &tls_sent);
+					if (status == CRYPT_ERROR_TIMEOUT) {
+						tls_sent = 0;
+						cryptPopData(session->tls_sess, "", 0, &status);
+						status = CRYPT_OK;
+					}
+					if(!HANDLE_CRYPT_CALL(status, session)) {
+						HANDLE_CRYPT_CALL(cryptFlushData(session->tls_sess), session);
+						if (failed)
+							*failed=TRUE;
+						return tls_sent;
+					}
+					result = tls_sent;
+				}
+				else {
+					result=sendsocket(session->socket,buf+sent,len-sent);
+					if(result==SOCKET_ERROR) {
+						if(ERROR_VALUE==ECONNRESET) 
+							lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",session->socket);
+						else if(ERROR_VALUE==ECONNABORTED) 
+							lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",session->socket);
 #ifdef EPIPE
-					else if(ERROR_VALUE==EPIPE) 
-						lprintf(LOG_NOTICE,"%04d Unable to send to peer",*sock);
+						else if(ERROR_VALUE==EPIPE) 
+							lprintf(LOG_NOTICE,"%04d Unable to send to peer",session->socket);
 #endif
-					else
-						lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",*sock,ERROR_VALUE);
-					if(failed)
-						*failed=TRUE;
-					return(sent);
+						else
+							lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",session->socket,ERROR_VALUE);
+						if(failed)
+							*failed=TRUE;
+						return(sent);
+					}
 				}
 				break;
 			case 0:
-				lprintf(LOG_WARNING,"%04d Timeout selecting socket for write",*sock);
+				lprintf(LOG_WARNING,"%04d Timeout selecting socket for write",session->socket);
 				if(failed)
 					*failed=TRUE;
 				return(sent);
 			case -1:
-				lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for write",*sock,ERROR_VALUE);
+				lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for write",session->socket,ERROR_VALUE);
 				if(failed)
 					*failed=TRUE;
 				return(sent);
@@ -578,6 +660,8 @@ static int sock_sendbuf(SOCKET *sock, const char *buf, size_t len, BOOL *failed)
 	}
 	if(failed && sent<len)
 		*failed=TRUE;
+	if(session->is_tls)
+		HANDLE_CRYPT_CALL(cryptFlushData(session->tls_sess), session);
 	return(sent);
 }
 
@@ -801,21 +885,35 @@ static time_t decode_date(char *date)
 	return(t);
 }
 
-static SOCKET open_socket(int type)
+static void open_socket(SOCKET sock, void *cbdata)
 {
 	char	error[256];
-	SOCKET	sock;
+#ifdef SO_ACCEPTFILTER
+	struct accept_filter_arg afa;
+#endif
 
-	sock=socket(AF_INET, type, IPPROTO_IP);
-	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
-		startup->socket_open(startup->cbdata,TRUE);
-	if(sock!=INVALID_SOCKET) {
+	startup->socket_open(startup->cbdata,TRUE);
+	if (cbdata != NULL && !strcmp(cbdata, "TLS")) {
+		if(set_socket_options(&scfg, sock, "web|http|tls", error, sizeof(error)))
+			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
+	}
+	else {
 		if(set_socket_options(&scfg, sock, "web|http", error, sizeof(error)))
 			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
-
-		sockets++;
 	}
-	return(sock);
+#ifdef SO_ACCEPTFILTER
+	memset(&afa, 0, sizeof(afa));
+	strcpy(afa.af_name, "httpready");
+	setsockopt(sock, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa));
+#endif
+
+	sockets++;
+}
+
+static void close_socket_cb(SOCKET sock, void *cbdata)
+{
+	startup->socket_open(startup->cbdata,FALSE);
+	sockets--;
 }
 
 static int close_socket(SOCKET *sock)
@@ -841,6 +939,31 @@ static int close_socket(SOCKET *sock)
 	return(result);
 }
 
+static int close_session_socket(http_session_t *session)
+{
+	char	buf[1];
+	int		len;
+
+	if(session==NULL || session->socket==INVALID_SOCKET)
+		return(-1);
+
+	if (session->is_tls) {
+		// First, wait for the ringbuffer to drain...
+		while(RingBufFull(&session->outbuf) && session->socket!=INVALID_SOCKET) {
+			HANDLE_CRYPT_CALL(cryptPopData(session->tls_sess, buf, 1, &len), session);
+			SLEEP(1);
+		}
+		// Now wait for tranmission to complete
+		while(pthread_mutex_trylock(&session->outbuf_write) == EBUSY) {
+			HANDLE_CRYPT_CALL(cryptPopData(session->tls_sess, buf, 1, &len), session);
+			SLEEP(1);
+		}
+		pthread_mutex_unlock(&session->outbuf_write);
+		HANDLE_CRYPT_CALL(cryptDestroySession(session->tls_sess), session);
+	}
+	return close_socket(&session->socket);
+}
+
 /* Waits for the outbuf to drain */
 static void drain_outbuf(http_session_t * session)
 {
@@ -921,7 +1044,7 @@ static void close_request(http_session_t * session)
 	 */
 	if((!session->req.keep_alive) || terminate_server) {
 		drain_outbuf(session);
-		close_socket(&session->socket);
+		close_session_socket(session);
 	}
 	if(session->socket==INVALID_SOCKET)
 		session->finished=TRUE;
@@ -1381,7 +1504,7 @@ void http_logon(http_session_t * session, user_t *usr)
 		/* Adjust Connect and host */
 		SAFECOPY(session->user.modem, session->client.protocol);
 		SAFECOPY(session->user.comp, session->host_name);
-		SAFECOPY(session->user.note, session->host_ip);
+		SAFECOPY(session->user.ipaddr, session->host_ip);
 		session->user.logontime = (time32_t)session->logon_time;
 		putuserdat(&scfg, &session->user);
 	}
@@ -1696,6 +1819,18 @@ static BOOL check_ars(http_session_t * session)
 	thisuser.number=i;
 	getuserdat(&scfg, &thisuser);
 	switch(session->req.auth.type) {
+		case AUTHENTICATION_TLS_PSK:
+			if((auth_allowed & (1<<AUTHENTICATION_TLS_PSK))==0)
+				return(FALSE);
+			if(session->last_user_num!=0) {
+				if(session->last_user_num>0)
+					http_logoff(session,session->socket,__LINE__);
+				session->user.number=0;
+				http_logon(session,NULL);
+			}
+			if(!http_checkuser(session))
+				return(FALSE);
+			break;
 		case AUTHENTICATION_BASIC:
 			if((auth_allowed & (1<<AUTHENTICATION_BASIC))==0)
 				return(FALSE);
@@ -1752,6 +1887,9 @@ static BOOL check_ars(http_session_t * session)
 
 	if(authorized)  {
 		switch(session->req.auth.type) {
+			case AUTHENTICATION_TLS_PSK:
+				add_env(session,"AUTH_TYPE","TLS-PSK");
+				break;
 			case AUTHENTICATION_BASIC:
 				add_env(session,"AUTH_TYPE","Basic");
 				break;
@@ -1800,6 +1938,46 @@ static named_string_t** read_ini_list(char* path, char* section, char* desc
 	return(list);
 }
 
+static int sess_recv(http_session_t *session, char *buf, size_t length, int flags)
+{
+	int len;
+	
+	if (session->is_tls) {
+		if (flags == MSG_PEEK) {
+			if (session->peeked_valid) {
+				buf[0] = session->peeked;
+				return 1;
+			}
+			if (HANDLE_CRYPT_CALL(cryptPopData(session->tls_sess, &session->peeked, 1, &len), session)) {
+				if (len == 1) {
+					session->peeked_valid = TRUE;
+					buf[0] = session->peeked;
+					return 1;
+				}
+				return 0;
+			}
+			return -1;
+		}
+		else {
+			if (session->peeked_valid) {
+				buf[0] = session->peeked;
+				session->peeked_valid = FALSE;
+				return 1;
+			}
+			if (HANDLE_CRYPT_CALL(cryptPopData(session->tls_sess, buf, length, &len), session)) {
+				if (len == 0) {
+					session->tls_pending = FALSE;
+				}
+				return len;
+			}
+			return -1;
+		}
+	}
+	else {
+		return recv(session->socket, buf, length, flags);
+	}
+}
+
 static int sockreadline(http_session_t * session, char *buf, size_t length)
 {
 	char	ch;
@@ -1812,37 +1990,41 @@ static int sockreadline(http_session_t * session, char *buf, size_t length)
 	for(i=0;TRUE;) {
 		if(session->socket==INVALID_SOCKET)
 			return(-1);
-		FD_ZERO(&rd_set);
-		FD_SET(session->socket,&rd_set);
-		/* Convert timeout from ms to sec/usec */
-		tv.tv_sec=startup->max_inactivity;
-		tv.tv_usec=0;
-		sel=select(session->socket+1,&rd_set,NULL,NULL,&tv);
-		switch(sel) {
-			case 1:
-				break;
-			case -1:
-				close_socket(&session->socket);
-				lprintf(LOG_DEBUG,"%04d !ERROR %d selecting socket for read",session->socket,ERROR_VALUE);
-				return(-1);
-			default:
-				/* Timeout */
-				lprintf(LOG_NOTICE,"%04d Session timeout due to inactivity (%d seconds)",session->socket,startup->max_inactivity);
-				return(-1);
+		if ((!session->is_tls) || (!session->tls_pending)) {
+			FD_ZERO(&rd_set);
+			FD_SET(session->socket,&rd_set);
+			/* Convert timeout from ms to sec/usec */
+			tv.tv_sec=startup->max_inactivity;
+			tv.tv_usec=0;
+			sel=select(session->socket+1,&rd_set,NULL,NULL,&tv);
+			switch(sel) {
+				case 1:
+					if (session->is_tls)
+						session->tls_pending=TRUE;
+					break;
+				case -1:
+					close_session_socket(session);
+					lprintf(LOG_DEBUG,"%04d !ERROR %d selecting socket for read",session->socket,ERROR_VALUE);
+					return(-1);
+				default:
+					/* Timeout */
+					lprintf(LOG_NOTICE,"%04d Session timeout due to inactivity (%d seconds)",session->socket,startup->max_inactivity);
+					return(-1);
+			}
 		}
 
-		switch(recv(session->socket, &ch, 1, 0)) {
+		switch(sess_recv(session, &ch, 1, 0)) {
 			case -1:
-				if(ERROR_VALUE!=EAGAIN) {
+				if(session->is_tls || ERROR_VALUE!=EAGAIN) {
 					if(startup->options&WEB_OPT_DEBUG_RX)
 						lprintf(LOG_DEBUG,"%04d !ERROR %d receiving on socket",session->socket,ERROR_VALUE);
-					close_socket(&session->socket);
+					close_session_socket(session);
 					return(-1);
 				}
 				break;
 			case 0:
 				/* Socket has been closed */
-				close_socket(&session->socket);
+				close_session_socket(session);
 				return(-1);
 		}
 
@@ -1936,7 +2118,7 @@ static int pipereadline(int pipe, char *buf, size_t length, char *fullbuf, size_
 	return(i);
 }
 
-int recvbufsocket(SOCKET *sock, char *buf, long count)
+static int recvbufsocket(http_session_t *session, char *buf, long count)
 {
 	int		rd=0;
 	int		i;
@@ -1947,14 +2129,14 @@ int recvbufsocket(SOCKET *sock, char *buf, long count)
 		return(0);
 	}
 
-	while(rd<count && socket_check(*sock,NULL,NULL,startup->max_inactivity*1000))  {
-		i=recv(*sock,buf+rd,count-rd,0);
+	while(rd<count && session_check(session,NULL,NULL,startup->max_inactivity*1000))  {
+		i=sess_recv(session,buf+rd,count-rd,0);
 		switch(i) {
 			case -1:
-				if(ERROR_VALUE!=EAGAIN)
-					close_socket(sock);
+				if(session->is_tls || ERROR_VALUE!=EAGAIN)
+					close_session_socket(session);
 			case 0:
-				close_socket(sock);
+				close_session_socket(session);
 				*buf=0;
 				return(0);
 		}
@@ -2250,124 +2432,127 @@ static BOOL parse_headers(http_session_t * session)
 			while(*value && *value<=' ') value++;
 			switch(i) {
 				case HEAD_AUTH:
-					if((p=strtok_r(value," ",&last))!=NULL) {
-						if(stricmp(p, "Basic")==0) {
-							p=strtok_r(NULL," ",&last);
-							if(p==NULL)
-								break;
-							while(*p && *p<' ') p++;
-							b64_decode(p,strlen(p),p,strlen(p));
-							p=strtok_r(p,":",&last);
-							if(p) {
-								if(strlen(p) >= sizeof(session->req.auth.username))
+					/* If you're authenticated via TLS-PSK, you can't use basic or digest */
+					if (session->req.auth.type != AUTHENTICATION_TLS_PSK) {
+						if((p=strtok_r(value," ",&last))!=NULL) {
+							if(stricmp(p, "Basic")==0) {
+								p=strtok_r(NULL," ",&last);
+								if(p==NULL)
 									break;
-								SAFECOPY(session->req.auth.username, p);
-								p=strtok_r(NULL,":",&last);
+								while(*p && *p<' ') p++;
+								b64_decode(p,strlen(p),p,strlen(p));
+								p=strtok_r(p,":",&last);
 								if(p) {
-									if(strlen(p) >= sizeof(session->req.auth.password))
+									if(strlen(p) >= sizeof(session->req.auth.username))
 										break;
-									SAFECOPY(session->req.auth.password, p);
-									session->req.auth.type=AUTHENTICATION_BASIC;
+									SAFECOPY(session->req.auth.username, p);
+									p=strtok_r(NULL,":",&last);
+									if(p) {
+										if(strlen(p) >= sizeof(session->req.auth.password))
+											break;
+										SAFECOPY(session->req.auth.password, p);
+										session->req.auth.type=AUTHENTICATION_BASIC;
+									}
 								}
 							}
-						}
-						else if(stricmp(p, "Digest")==0) {
-							p=strtok_r(NULL, "", &last);
-							/* Defaults */
-							session->req.auth.algorithm=ALGORITHM_MD5;
-							session->req.auth.type=AUTHENTICATION_DIGEST;
-							/* Parse out values one at a time and store */
-							while(*p) {
-								while(isspace(*p))
-									p++;
-								if(strnicmp(p,"username=",9)==0) {
-									p+=9;
-									tvalue=get_token_value(&p);
-									if(strlen(tvalue) >= sizeof(session->req.auth.username))
-										break;
-									SAFECOPY(session->req.auth.username, tvalue);
-								}
-								else if(strnicmp(p,"realm=",6)==0) {
-									p+=6;
-									session->req.auth.realm=strdup(get_token_value(&p));
-								}
-								else if(strnicmp(p,"nonce=",6)==0) {
-									p+=6;
-									session->req.auth.nonce=strdup(get_token_value(&p));
-								}
-								else if(strnicmp(p,"uri=",4)==0) {
-									p+=4;
-									session->req.auth.digest_uri=strdup(get_token_value(&p));
-								}
-								else if(strnicmp(p,"response=",9)==0) {
-									p+=9;
-									tvalue=get_token_value(&p);
-									if(strlen(tvalue)==32) {
-										for(i=0; i<16; i++) {
-											session->req.auth.digest[i]=hexval(tvalue[i*2])<<4 | hexval(tvalue[i*2+1]);
+							else if(stricmp(p, "Digest")==0) {
+								p=strtok_r(NULL, "", &last);
+								/* Defaults */
+								session->req.auth.algorithm=ALGORITHM_MD5;
+								session->req.auth.type=AUTHENTICATION_DIGEST;
+								/* Parse out values one at a time and store */
+								while(*p) {
+									while(isspace(*p))
+										p++;
+									if(strnicmp(p,"username=",9)==0) {
+										p+=9;
+										tvalue=get_token_value(&p);
+										if(strlen(tvalue) >= sizeof(session->req.auth.username))
+											break;
+										SAFECOPY(session->req.auth.username, tvalue);
+									}
+									else if(strnicmp(p,"realm=",6)==0) {
+										p+=6;
+										session->req.auth.realm=strdup(get_token_value(&p));
+									}
+									else if(strnicmp(p,"nonce=",6)==0) {
+										p+=6;
+										session->req.auth.nonce=strdup(get_token_value(&p));
+									}
+									else if(strnicmp(p,"uri=",4)==0) {
+										p+=4;
+										session->req.auth.digest_uri=strdup(get_token_value(&p));
+									}
+									else if(strnicmp(p,"response=",9)==0) {
+										p+=9;
+										tvalue=get_token_value(&p);
+										if(strlen(tvalue)==32) {
+											for(i=0; i<16; i++) {
+												session->req.auth.digest[i]=hexval(tvalue[i*2])<<4 | hexval(tvalue[i*2+1]);
+											}
 										}
 									}
-								}
-								else if(strnicmp(p,"algorithm=",10)==0) {
-									p+=10;
-									tvalue=get_token_value(&p);
-									if(stricmp(tvalue,"MD5")==0) {
-										session->req.auth.algorithm=ALGORITHM_MD5;
+									else if(strnicmp(p,"algorithm=",10)==0) {
+										p+=10;
+										tvalue=get_token_value(&p);
+										if(stricmp(tvalue,"MD5")==0) {
+											session->req.auth.algorithm=ALGORITHM_MD5;
+										}
+										else {
+											session->req.auth.algorithm=ALGORITHM_UNKNOWN;
+										}
 									}
-									else {
-										session->req.auth.algorithm=ALGORITHM_UNKNOWN;
+									else if(strnicmp(p,"cnonce=",7)==0) {
+										p+=7;
+										session->req.auth.cnonce=strdup(get_token_value(&p));
 									}
-								}
-								else if(strnicmp(p,"cnonce=",7)==0) {
-									p+=7;
-									session->req.auth.cnonce=strdup(get_token_value(&p));
-								}
-								else if(strnicmp(p,"qop=",4)==0) {
-									p+=4;
-									tvalue=get_token_value(&p);
-									if(stricmp(tvalue,"auth")==0) {
-										session->req.auth.qop_value=QOP_AUTH;
+									else if(strnicmp(p,"qop=",4)==0) {
+										p+=4;
+										tvalue=get_token_value(&p);
+										if(stricmp(tvalue,"auth")==0) {
+											session->req.auth.qop_value=QOP_AUTH;
+										}
+										else if (stricmp(tvalue,"auth-int")==0) {
+											session->req.auth.qop_value=QOP_AUTH_INT;
+										}
+										else {
+											session->req.auth.qop_value=QOP_UNKNOWN;
+										}
 									}
-									else if (stricmp(tvalue,"auth-int")==0) {
-										session->req.auth.qop_value=QOP_AUTH_INT;
+									else if(strnicmp(p,"nc=",3)==0) {
+										p+=3;
+										session->req.auth.nonce_count=strdup(get_token_value(&p));
 									}
 									else {
-										session->req.auth.qop_value=QOP_UNKNOWN;
+										while(*p && *p != '=')
+											p++;
+										if(*p == '=')
+											get_token_value(&p);
 									}
 								}
-								else if(strnicmp(p,"nc=",3)==0) {
-									p+=3;
-									session->req.auth.nonce_count=strdup(get_token_value(&p));
-								}
-								else {
-									while(*p && *p != '=')
-										p++;
-									if(*p == '=')
-										get_token_value(&p);
-								}
-							}
-							if(session->req.auth.digest_uri==NULL)
-								session->req.auth.digest_uri=strdup(session->req.request_line);
-							/* Validate that we have the required values... */
-							switch(session->req.auth.qop_value) {
-								case QOP_NONE:
-									if(session->req.auth.realm==NULL
-											|| session->req.auth.nonce==NULL
-											|| session->req.auth.digest_uri==NULL)
-										send_error(session,"400 Bad Request");
-									break;
-								case QOP_AUTH:
-								case QOP_AUTH_INT:
-									if(session->req.auth.realm==NULL
-											|| session->req.auth.nonce==NULL
-											|| session->req.auth.nonce_count==NULL
-											|| session->req.auth.cnonce==NULL
-											|| session->req.auth.digest_uri==NULL)
+								if(session->req.auth.digest_uri==NULL)
+									session->req.auth.digest_uri=strdup(session->req.request_line);
+								/* Validate that we have the required values... */
+								switch(session->req.auth.qop_value) {
+									case QOP_NONE:
+										if(session->req.auth.realm==NULL
+												|| session->req.auth.nonce==NULL
+												|| session->req.auth.digest_uri==NULL)
+											send_error(session,"400 Bad Request");
+										break;
+									case QOP_AUTH:
+									case QOP_AUTH_INT:
+										if(session->req.auth.realm==NULL
+												|| session->req.auth.nonce==NULL
+												|| session->req.auth.nonce_count==NULL
+												|| session->req.auth.cnonce==NULL
+												|| session->req.auth.digest_uri==NULL)
+											send_error(session,"400 Bad Request");
+										break;
+									default:
 										send_error(session,"400 Bad Request");
-									break;
-								default:
-									send_error(session,"400 Bad Request");
-									break;
+										break;
+								}
 							}
 						}
 					}
@@ -2683,10 +2868,10 @@ static BOOL get_request_headers(http_session_t * session)
 
 	while(sockreadline(session,head_line,sizeof(head_line)-1)>0) {
 		/* Multi-line headers */
-		while((i=recv(session->socket,&next_char,1,MSG_PEEK))>0
+		while((i=sess_recv(session,&next_char,1,MSG_PEEK))>0
 			&& (next_char=='\t' || next_char==' ')) {
-			if(i==-1 && ERROR_VALUE != EAGAIN)
-				close_socket(&session->socket);
+			if(i==-1 && (session->is_tls || ERROR_VALUE != EAGAIN))
+				close_session_socket(session);
 			i=strlen(head_line);
 			if(i>sizeof(head_line)-1) {
 				lprintf(LOG_ERR,"%04d !ERROR long multi-line header. The web server is broken!", session->socket);
@@ -2919,6 +3104,43 @@ static BOOL check_extra_path(http_session_t * session)
 	return(FALSE);
 }
 
+static void read_webctrl_section(FILE *file, char *section, http_session_t *session, BOOL *recheck_dynamic)
+{
+	char	str[MAX_PATH+1];
+
+	if(iniReadString(file, section, "AccessRequirements", session->req.ars,str)==str)
+		SAFECOPY(session->req.ars,str);
+	if(iniReadString(file, section, "Realm", scfg.sys_name,str)==str) {
+		FREE_AND_NULL(session->req.realm);
+		/* FREE()d in close_request() */
+		session->req.realm=strdup(str);
+	}
+	if(iniReadString(file, section, "DigestRealm", scfg.sys_name,str)==str) {
+		FREE_AND_NULL(session->req.digest_realm);
+		/* FREE()d in close_request() */
+		session->req.digest_realm=strdup(str);
+	}
+	if(iniReadString(file, section, "ErrorDirectory", error_dir,str)==str) {
+		prep_dir(root_dir, str, sizeof(str));
+		FREE_AND_NULL(session->req.error_dir);
+		/* FREE()d in close_request() */
+		session->req.error_dir=strdup(str);
+	}
+	if(iniReadString(file, section, "CGIDirectory", cgi_dir,str)==str) {
+		prep_dir(root_dir, str, sizeof(str));
+		FREE_AND_NULL(session->req.cgi_dir);
+		/* FREE()d in close_request() */
+		session->req.cgi_dir=strdup(str);
+		*recheck_dynamic=TRUE;
+	}
+	if(iniReadString(file, section, "Authentication", default_auth_list,str)==str) {
+		FREE_AND_NULL(session->req.auth_list);
+		/* FREE()d in close_request() */
+		session->req.auth_list=strdup(str);
+	}
+	session->req.path_info_index=iniReadBool(file, section, "PathInfoIndex", FALSE);
+}
+
 static BOOL check_request(http_session_t * session)
 {
 	char	path[MAX_PATH+1];
@@ -2935,6 +3157,8 @@ static BOOL check_request(http_session_t * session)
 	char	*spec;
 	str_list_t	specs;
 	BOOL	recheck_dynamic=FALSE;
+	char	*spath, *sp, *nsp, *pspec;
+	size_t	len;
 
 	if(session->req.finished)
 		return(FALSE);
@@ -3043,71 +3267,28 @@ static BOOL check_request(http_session_t * session)
 				/* FREE()d in this block */
 				specs=iniReadSectionList(file,NULL);
 				/* Read in globals */
-				if(iniReadString(file, NULL, "AccessRequirements", session->req.ars,str)==str)
-					SAFECOPY(session->req.ars,str);
-				if(iniReadString(file, NULL, "Realm", scfg.sys_name,str)==str) {
-					FREE_AND_NULL(session->req.realm);
-					/* FREE()d in close_request() */
-					session->req.realm=strdup(str);
-				}
-				if(iniReadString(file, NULL, "DigestRealm", scfg.sys_name,str)==str) {
-					FREE_AND_NULL(session->req.digest_realm);
-					/* FREE()d in close_request() */
-					session->req.digest_realm=strdup(str);
-				}
-				if(iniReadString(file, NULL, "ErrorDirectory", error_dir,str)==str) {
-					prep_dir(root_dir, str, sizeof(str));
-					FREE_AND_NULL(session->req.error_dir);
-					/* FREE()d in close_request() */
-					session->req.error_dir=strdup(str);
-				}
-				if(iniReadString(file, NULL, "CGIDirectory", cgi_dir,str)==str) {
-					prep_dir(root_dir, str, sizeof(str));
-					FREE_AND_NULL(session->req.cgi_dir);
-					/* FREE()d in close_request() */
-					session->req.cgi_dir=strdup(str);
-					recheck_dynamic=TRUE;
-				}
-				if(iniReadString(file, NULL, "Authentication", default_auth_list,str)==str) {
-					FREE_AND_NULL(session->req.auth_list);
-					/* FREE()d in close_request() */
-					session->req.auth_list=strdup(str);
-				}
-				session->req.path_info_index=iniReadBool(file, NULL, "PathInfoIndex", FALSE);
+				read_webctrl_section(file, NULL, session, &recheck_dynamic);
 				/* Read in per-filespec */
 				while((spec=strListPop(&specs))!=NULL) {
-					if(wildmatch(filename,spec,TRUE)) {
-						if(iniReadString(file, spec, "AccessRequirements", session->req.ars,str)==str)
-							SAFECOPY(session->req.ars,str);
-						if(iniReadString(file, spec, "Realm", scfg.sys_name,str)==str) {
-							FREE_AND_NULL(session->req.realm);
-							/* FREE()d in close_request() */
-							session->req.realm=strdup(str);
-						}
-						if(iniReadString(file, spec, "DigestRealm", scfg.sys_name,str)==str) {
-							FREE_AND_NULL(session->req.digest_realm);
-							/* FREE()d in close_request() */
-							session->req.digest_realm=strdup(str);
-						}
-						if(iniReadString(file, spec, "ErrorDirectory", error_dir,str)==str) {
-							FREE_AND_NULL(session->req.error_dir);
-							prep_dir(root_dir, str, sizeof(str));
-							/* FREE()d in close_request() */
-							session->req.error_dir=strdup(str);
-						}
-						if(iniReadString(file, spec, "CGIDirectory", cgi_dir,str)==str) {
-							FREE_AND_NULL(session->req.cgi_dir);
-							prep_dir(root_dir, str, sizeof(str));
-							/* FREE()d in close_request() */
-							session->req.cgi_dir=strdup(str);
-							recheck_dynamic=TRUE;
-						}
-						if(iniReadString(file, spec, "Authentication", default_auth_list,str)==str) {
-							FREE_AND_NULL(session->req.auth_list);
-							/* FREE()d in close_request() */
-							session->req.auth_list=strdup(str);
+					len=strlen(spec);
+					if(spec[0] && IS_PATH_DELIM(spec[len-1])) {
+						/* Search for matching path elements... */
+						spath=strdup(path+(p-curdir+1));
+						pspec=strdup(spec);
+						pspec[len-1]=0;
+						for(sp=spath, nsp=find_first_slash(sp+1); nsp; nsp=find_first_slash(sp+1)) {
+							*nsp=0;
+							nsp++;
+							if(wildmatch(sp, pspec, TRUE)) {
+								read_webctrl_section(file, spec, session, &recheck_dynamic);
+							}
+							sp=nsp;
 						}
-						session->req.path_info_index=iniReadBool(file, spec, "PathInfoIndex", FALSE);
+						free(spath);
+						free(pspec);
+					}
+					else if(wildmatch(filename,spec,TRUE)) {
+						read_webctrl_section(file, spec, session, &recheck_dynamic);
 					}
 					free(spec);
 				}
@@ -3714,7 +3895,7 @@ static BOOL exec_cgi(http_session_t *session)
 
 	SAFECOPY(cgi_status,session->req.status);
 	SAFEPRINTF2(content_type,"%s: %s",get_header(HEAD_TYPE),startup->default_cgi_content);
-	while(server_socket!=INVALID_SOCKET) {
+	while(!terminated) {
 
 		if(WaitForSingleObject(process_info.hProcess,0)==WAIT_OBJECT_0)
 			process_terminated=TRUE;	/* handle remaining data in pipe before breaking */
@@ -3726,13 +3907,13 @@ static BOOL exec_cgi(http_session_t *session)
 		}
 
 		/* Check socket for received POST Data */
-		if(!socket_check(session->socket, &rd, NULL, /* timeout: */0)) {
+		if(!session_check(session, &rd, NULL, /* timeout: */0)) {
 			lprintf(LOG_WARNING,"%04d CGI Socket disconnected", session->socket);
 			break;
 		}
 		if(rd) {
 			/* Send received POST Data to stdin of CGI process */
-			if((i=recv(session->socket, buf, sizeof(buf), 0)) > 0)  {
+			if((i=sess_recv(session, buf, sizeof(buf), 0)) > 0)  {
 				lprintf(LOG_DEBUG,"%04d CGI Received %d bytes of POST data"
 					,session->socket, i);
 				WriteFile(wrpipe, buf, i, &wr, /* Overlapped: */NULL);
@@ -4526,6 +4707,9 @@ static JSContext*
 js_initcx(http_session_t *session)
 {
 	JSContext*	js_cx;
+	jsval		val;
+	JSObject*	obj;
+	js_socket_private_t* p;
 
 	lprintf(LOG_DEBUG,"%04d JavaScript: Initializing context (stack: %lu bytes)"
 		,session->socket,startup->js.cx_stack);
@@ -4559,6 +4743,14 @@ js_initcx(http_session_t *session)
 		JS_DestroyContext(js_cx);
 		return(NULL);
 	}
+	if (session->is_tls) {
+		JS_GetProperty(js_cx, session->js_glob, "client", &val);
+		obj=JSVAL_TO_OBJECT(val);
+		JS_GetProperty(js_cx, obj, "socket", &val);
+		obj=JSVAL_TO_OBJECT(val);
+		p=(js_socket_private_t*)JS_GetPrivate(js_cx,obj);
+		p->session=session->tls_sess;
+	}
 
 	return(js_cx);
 }
@@ -4567,7 +4759,6 @@ static BOOL js_setup(http_session_t* session)
 {
 	JSObject*	argv;
 
-#ifndef ONE_JS_RUNTIME
 	if(session->js_runtime == NULL) {
 		lprintf(LOG_DEBUG,"%04d JavaScript: Creating runtime: %lu bytes"
 			,session->socket,startup->js.max_bytes);
@@ -4577,7 +4768,6 @@ static BOOL js_setup(http_session_t* session)
 			return(FALSE);
 		}
 	}
-#endif
 
 	if(session->js_cx==NULL) {	/* Context not yet created, create it now */
 		/* js_initcx() begins a context */
@@ -4823,7 +5013,7 @@ BOOL post_to_file(http_session_t *session, FILE*fp, size_t ch_len)
 	int			bytes_read;
 
 	for(k=0; k<ch_len;) {
-		bytes_read=recvbufsocket(&session->socket,buf,(ch_len-k)>sizeof(buf)?sizeof(buf):(ch_len-k));
+		bytes_read=recvbufsocket(session,buf,(ch_len-k)>sizeof(buf)?sizeof(buf):(ch_len-k));
 		if(!bytes_read) {
 			send_error(session,error_500);
 			fclose(fp);
@@ -4868,7 +5058,7 @@ FILE *open_post_file(http_session_t *session)
 
 int read_post_data(http_session_t * session)
 {
-	unsigned	i=0;
+	uint64_t	i=0;
 	FILE		*fp=NULL;
 
 	if(session->req.dynamic!=IS_CGI && (session->req.post_len || session->req.read_chunked))  {
@@ -4919,7 +5109,7 @@ int read_post_data(http_session_t * session)
 					}
 					session->req.post_data=p;
 					/* read new data */
-					bytes_read=recvbufsocket(&session->socket,session->req.post_data+session->req.post_len,ch_len);
+					bytes_read=recvbufsocket(session,session->req.post_data+session->req.post_len,ch_len);
 					if(!bytes_read) {
 						send_error(session,error_500);
 						if(fp) fclose(fp);
@@ -4963,7 +5153,7 @@ int read_post_data(http_session_t * session)
 			else {
 				/* FREE()d in close_request()  */
 				if(i < (MAX_POST_LEN+1) && (session->req.post_data=malloc(i+1)) != NULL)
-					session->req.post_len=recvbufsocket(&session->socket,session->req.post_data,i);
+					session->req.post_len=recvbufsocket(session,session->req.post_data,i);
 				else  {
 					lprintf(LOG_CRIT,"%04d !ERROR Allocating %d bytes of memory",session->socket,i);
 					send_error(session,"413 Request entity too large");
@@ -5001,7 +5191,7 @@ void http_output_thread(void *arg)
 	/* Destroyed at end of function */
 	if((i=pthread_mutex_init(&session->outbuf_write,NULL))!=0) {
 		lprintf(LOG_DEBUG,"Error %d initializing outbuf mutex",i);
-		close_socket(&session->socket);
+		close_session_socket(session);
 		thread_down();
 		return;
 	}
@@ -5084,6 +5274,7 @@ void http_output_thread(void *arg)
 
 		pthread_mutex_lock(&session->outbuf_write);
         RingBufRead(obuf, (uchar*)bufdata, avail);
+		pthread_mutex_unlock(&session->outbuf_write);
 		if(chunked) {
 			bufdata+=avail;
 			*(bufdata++)='\r';
@@ -5092,8 +5283,7 @@ void http_output_thread(void *arg)
 		}
 
 		if(!failed)
-			sock_sendbuf(&session->socket, buf, len, &failed);
-		pthread_mutex_unlock(&session->outbuf_write);
+			sess_sendbuf(session, buf, len, &failed);
     }
 	thread_down();
 	/* Ensure outbuf isn't currently being drained */
@@ -5106,8 +5296,6 @@ void http_output_thread(void *arg)
 
 void http_session_thread(void* arg)
 {
-	char*			host_name;
-	HOSTENT*		host;
 	SOCKET			socket;
 	char			redir_req[MAX_REQUEST_LINE+1];
 	char			*redirp;
@@ -5116,6 +5304,9 @@ void http_session_thread(void* arg)
 	ulong			login_attempts=0;
 	BOOL			init_error;
 	int32_t			clients_remain;
+	int				i;
+	int				last;
+	user_t			user;
 
 	SetThreadName("HTTP Session");
 	pthread_mutex_lock(&((http_session_t*)arg)->struct_filled);
@@ -5144,11 +5335,46 @@ void http_session_thread(void* arg)
 
 	session.finished=FALSE;
 
+	if (session.is_tls) {
+		/* Create and initialize the TLS session */
+		if (!HANDLE_CRYPT_CALL(cryptCreateSession(&session.tls_sess, CRYPT_UNUSED, CRYPT_SESSION_SSL_SERVER), &session)) {
+			close_session_socket(&session);
+			thread_down();
+			return;
+		}
+		/* Add all the user/password combinations */
+#if 0 // TLS-PSK is currently broken in cryptlib
+		last = lastuser(&scfg);
+		for (i=1; i <= last; i++) {
+			user.number = i;
+			getuserdat(&scfg,&user);
+			if(user.misc&(DELETED|INACTIVE))
+				continue;
+			if (user.alias[0] && user.pass[0]) {
+				if(HANDLE_CRYPT_CALL(cryptSetAttributeString(session.tls_sess, CRYPT_SESSINFO_USERNAME, user.alias, strlen(user.alias)), &session))
+					HANDLE_CRYPT_CALL(cryptSetAttributeString(session.tls_sess, CRYPT_SESSINFO_PASSWORD, user.pass, strlen(user.pass)), &session);
+			}
+		}
+#endif
+		if (tls_context != -1) {
+			HANDLE_CRYPT_CALL(cryptSetAttribute(session.tls_sess, CRYPT_SESSINFO_PRIVATEKEY, tls_context), &session);
+		}
+		BOOL nodelay=TRUE;
+		setsockopt(session.socket,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
+
+		HANDLE_CRYPT_CALL(cryptSetAttribute(session.tls_sess, CRYPT_SESSINFO_NETWORKSOCKET, session.socket), &session);
+		if (!HANDLE_CRYPT_CALL(cryptSetAttribute(session.tls_sess, CRYPT_SESSINFO_ACTIVE, 1), &session)) {
+			close_session_socket(&session);
+			thread_down();
+			return;
+		}
+	}
+
 	/* Start up the output buffer */
 	/* FREE()d in this block (RingBufDispose before all returns) */
 	if(RingBufInit(&(session.outbuf), OUTBUF_LEN)) {
 		lprintf(LOG_ERR,"%04d Canot create output ringbuffer!", session.socket);
-		close_socket(&session.socket);
+		close_session_socket(&session);
 		thread_down();
 		return;
 	}
@@ -5160,18 +5386,8 @@ void http_session_thread(void* arg)
 
 	sbbs_srand();	/* Seed random number generator */
 
-	if(startup->options&BBS_OPT_NO_HOST_LOOKUP)
-		host=NULL;
-	else
-		host=gethostbyaddr ((char *)&session.addr.sin_addr
-			,sizeof(session.addr.sin_addr),AF_INET);
-
-	if(host!=NULL && host->h_name!=NULL)
-		host_name=host->h_name;
-	else
-		host_name=session.host_ip;
-
-	SAFECOPY(session.host_name,host_name);
+	if(getnameinfo(&session.addr.addr, session.addr_len, session.host_name, sizeof(session.host_name), NULL, 0, (startup->options&BBS_OPT_NO_HOST_LOOKUP)?NI_NUMERICHOST:0)!=0)
+		SAFECOPY(session.host_name, session.host_ip);
 
 	if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP))  {
 		lprintf(LOG_INFO,"%04d Hostname: %s", session.socket, session.host_name);
@@ -5183,7 +5399,7 @@ void http_session_thread(void* arg)
 #endif
 		if(trashcan(&scfg,session.host_name,"host")) {
 			lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in host.can: %s", session.socket, session.host_name);
-			close_socket(&session.socket);
+			close_session_socket(&session);
 			sem_wait(&session.output_thread_terminated);
 			sem_destroy(&session.output_thread_terminated);
 			RingBufDispose(&session.outbuf);
@@ -5195,7 +5411,7 @@ void http_session_thread(void* arg)
 	/* host_ip wasn't defined in http_session_thread */
 	if(trashcan(&scfg,session.host_ip,"ip")) {
 		lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", session.socket, session.host_ip);
-		close_socket(&session.socket);
+		close_session_socket(&session);
 		sem_wait(&session.output_thread_terminated);
 		sem_destroy(&session.output_thread_terminated);
 		RingBufDispose(&session.outbuf);
@@ -5209,9 +5425,9 @@ void http_session_thread(void* arg)
 
 	SAFECOPY(session.client.addr,session.host_ip);
 	SAFECOPY(session.client.host,session.host_name);
-	session.client.port=ntohs(session.addr.sin_port);
+	session.client.port=inet_addrport(&session.addr);
 	session.client.time=time32(NULL);
-	session.client.protocol="HTTP";
+	session.client.protocol=session.is_tls ? "HTTP":"HTTPS";
 	session.client.user=session.username;
 	session.client.size=sizeof(session.client);
 	client_on(session.socket, &session.client, /* update existing client record? */FALSE);
@@ -5232,6 +5448,14 @@ void http_session_thread(void* arg)
 	while(!session.finished) {
 		init_error=FALSE;
 	    memset(&(session.req), 0, sizeof(session.req));
+	    if (session.is_tls) {
+#if 0 // TLS-PSK is currently broken in cryptlib
+			if (cryptGetAttributeString(session.tls_sess, CRYPT_SESSINFO_USERNAME, session.req.auth.username, &i)==CRYPT_OK) {
+				session.req.auth.username[i]=0;
+				session.req.auth.type = AUTHENTICATION_TLS_PSK;
+			}
+#endif
+		}
 		redirp=NULL;
 		loop_count=0;
 		if(startup->options&WEB_OPT_HTTP_LOGGING) {
@@ -5310,20 +5534,18 @@ void http_session_thread(void* arg)
 		session.js_cx=NULL;
 	}
 
-#ifndef ONE_JS_RUNTIME
 	if(session.js_runtime!=NULL) {
 		lprintf(LOG_DEBUG,"%04d JavaScript: Destroying runtime",socket);
 		jsrt_Release(session.js_runtime);
 		session.js_runtime=NULL;
 	}
-#endif
 
 #ifdef _WIN32
 	if(startup->hangup_sound[0] && !(startup->options&BBS_OPT_MUTE)) 
 		PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME);
 #endif
 
-	close_socket(&session.socket);
+	close_session_socket(&session);
 	sem_wait(&session.output_thread_terminated);
 	sem_destroy(&session.output_thread_terminated);
 	RingBufDispose(&session.outbuf);
@@ -5345,7 +5567,7 @@ void http_session_thread(void* arg)
 
 void DLLCALL web_terminate(void)
 {
-   	lprintf(LOG_INFO,"%04d Web Server terminate",server_socket);
+   	lprintf(LOG_INFO,"Web Server terminate");
 	terminate_server=TRUE;
 }
 
@@ -5370,9 +5592,16 @@ static void cleanup(int code)
 
 	semfile_list_free(&recycle_semfiles);
 	semfile_list_free(&shutdown_semfiles);
+	
+	if (tls_context != -1) {
+		cryptDestroyContext(tls_context);
+		tls_context = -1;
+	}
 
-	if(server_socket!=INVALID_SOCKET) {
-		close_socket(&server_socket);
+	if(!terminated) {
+		xpms_destroy(ws_set, close_socket_cb, NULL);
+		ws_set=NULL;
+		terminated=TRUE;
 	}
 
 	update_clients();	/* active_clients is destroyed below */
@@ -5437,7 +5666,7 @@ void http_logging_thread(void* arg)
 
 	thread_up(TRUE /* setuid */);
 
-	lprintf(LOG_INFO,"%04d HTTP logging thread started", server_socket);
+	lprintf(LOG_INFO,"HTTP logging thread started");
 
 	for(;;) {
 		struct log_data *ld;
@@ -5458,8 +5687,7 @@ void http_logging_thread(void* arg)
 		if(ld==NULL) {
 			if(terminate_http_logging_thread)
 				break;
-			lprintf(LOG_ERR,"%04d HTTP logging thread received NULL linked list log entry"
-				,server_socket);
+			lprintf(LOG_ERR,"HTTP logging thread received NULL linked list log entry");
 			continue;
 		}
 		SAFECOPY(newfilename,base);
@@ -5475,7 +5703,7 @@ void http_logging_thread(void* arg)
 			SAFECOPY(filename,newfilename);
 			logfile=fopen(filename,"ab");
 			if(logfile)
-				lprintf(LOG_INFO,"%04d HTTP logfile is now: %s",server_socket,filename);
+				lprintf(LOG_INFO,"HTTP logfile is now: %s",filename);
 		}
 		if(logfile!=NULL) {
 			if(ld->status) {
@@ -5502,7 +5730,7 @@ void http_logging_thread(void* arg)
 			}
 		}
 		else {
-			lprintf(LOG_ERR,"%04d HTTP server failed to open logfile %s (%d)!",server_socket,filename,errno);
+			lprintf(LOG_ERR,"HTTP server failed to open logfile %s (%d)!",filename,errno);
 		}
 		FREE_AND_NULL(ld->hostname);
 		FREE_AND_NULL(ld->ident);
@@ -5518,41 +5746,32 @@ void http_logging_thread(void* arg)
 		logfile=NULL;
 	}
 	thread_down();
-	lprintf(LOG_INFO,"%04d HTTP logging thread terminated",server_socket);
+	lprintf(LOG_INFO,"HTTP logging thread terminated");
 
 	http_logging_thread_running=FALSE;
 }
 
 void DLLCALL web_server(void* arg)
 {
-	int				i;
-	int				result;
 	time_t			start;
 	WORD			host_port;
-	char			host_ip[32];
+	char			host_ip[INET6_ADDRSTRLEN];
 	char			path[MAX_PATH+1];
 	char			logstr[256];
 	char			mime_types_ini[MAX_PATH+1];
 	char			web_handler_ini[MAX_PATH+1];
-	SOCKADDR_IN		server_addr={0};
-	SOCKADDR_IN		client_addr;
+	union xp_sockaddr	client_addr;
 	socklen_t		client_addr_len;
 	SOCKET			client_socket;
-	SOCKET			high_socket_set;
-	fd_set			socket_set;
 	time_t			t;
 	time_t			initialized=0;
 	FILE*			fp;
 	char*			p;
 	char			compiler[32];
 	http_session_t *	session=NULL;
-	struct timeval tv;
-#ifdef ONE_JS_RUNTIME
-	JSRuntime*      js_runtime;
-#endif
-#ifdef SO_ACCEPTFILTER
-	struct accept_filter_arg afa;
-#endif
+	struct in_addr	iaddr;
+	void			*acc_type;
+	char			ssl_estr[SSL_ESTR_LEN];
 
 	startup=(web_startup_t*)arg;
 
@@ -5598,7 +5817,8 @@ void DLLCALL web_server(void* arg)
 	js_server_props.version_detail=web_ver();
 	js_server_props.clients=&active_clients.value;
 	js_server_props.options=&startup->options;
-	js_server_props.interface_addr=&startup->interface_addr;
+	/* TODO IPv6 */
+	js_server_props.interface_addr=&startup->outgoing4;
 
 	uptime=0;
 	served=0;
@@ -5684,6 +5904,11 @@ void DLLCALL web_server(void* arg)
 		lprintf(LOG_DEBUG,"Error directory: %s", error_dir);
 		lprintf(LOG_DEBUG,"CGI directory: %s", cgi_dir);
 
+		lprintf(LOG_DEBUG,"Loading/Creating TLS certificate");
+		tls_context = get_ssl_cert(&scfg, ssl_estr);
+		if (tls_context == -1)
+			lprintf(LOG_ERR, "Error creating TLS certificate: %s", ssl_estr);
+
 		iniFileName(mime_types_ini,sizeof(mime_types_ini),scfg.ctrl_dir,"mime_types.ini");
 		mime_types=read_ini_list(mime_types_ini,NULL /* root section */,"MIME types"
 			,mime_types);
@@ -5712,65 +5937,21 @@ void DLLCALL web_server(void* arg)
 		update_clients();
 
 		/* open a socket and wait for a client */
-
-		server_socket = open_socket(SOCK_STREAM);
-
-		if(server_socket == INVALID_SOCKET) {
-			lprintf(LOG_CRIT,"!ERROR %d creating HTTP socket", ERROR_VALUE);
-			cleanup(1);
-			return;
-		}
+		ws_set = xpms_create(startup->bind_retry_count, startup->bind_retry_delay, lprintf);
 		
-/*
- *		i=1;
- *		if(setsockopt(server_socket, IPPROTO_TCP, TCP_NOPUSH, &i, sizeof(i)))
- *			lprintf("Cannot set TCP_NOPUSH socket option");
- */
-
-#ifdef SO_ACCEPTFILTER
-		memset(&afa, 0, sizeof(afa));
-		strcpy(afa.af_name, "httpready");
-		setsockopt(server_socket, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa));
-#endif
-
-		lprintf(LOG_DEBUG,"%04d Web Server socket opened",server_socket);
-
-		/*****************************/
-		/* Listen for incoming calls */
-		/*****************************/
-		memset(&server_addr, 0, sizeof(server_addr));
-
-		server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
-		server_addr.sin_family = AF_INET;
-		server_addr.sin_port   = htons(startup->port);
-
-		if(startup->port < IPPORT_RESERVED) {
-			if(startup->seteuid!=NULL)
-				startup->seteuid(FALSE);
-		}
-		result = retry_bind(server_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
-			,startup->bind_retry_count,startup->bind_retry_delay,"Web Server",lprintf);
-		if(startup->port < IPPORT_RESERVED) {
-			if(startup->seteuid!=NULL)
-				startup->seteuid(TRUE);
-		}
-		if(result != 0) {
-			lprintf(LOG_CRIT,"%s",BIND_FAILURE_HELP);
+		if(ws_set == NULL) {
+			lprintf(LOG_CRIT,"!ERROR %d creating HTTP socket set", ERROR_VALUE);
 			cleanup(1);
 			return;
 		}
+		lprintf(LOG_DEBUG,"Web Server socket set created");
 
-		result = listen(server_socket, 64);
-
-		if(result != 0) {
-			lprintf(LOG_CRIT,"%04d !ERROR %d (%d) listening on socket"
-				,server_socket, result, ERROR_VALUE);
-			cleanup(1);
-			return;
-		}
-		lprintf(LOG_INFO,"%04d Web Server listening on port %u"
-			,server_socket, startup->port);
-		status("Listening");
+		/*
+		 * Add interfaces
+		 */
+		xpms_add_list(ws_set, PF_UNSPEC, SOCK_STREAM, 0, startup->interfaces, startup->port, "Web Server", open_socket, startup->seteuid, NULL);
+		if(do_cryptInit())
+			xpms_add_list(ws_set, PF_UNSPEC, SOCK_STREAM, 0, startup->tls_interfaces, startup->tls_port, "Secure Web Server", open_socket, startup->seteuid, "TLS");
 
 		listInit(&log_list,/* flags */ LINK_LIST_MUTEX|LINK_LIST_SEMAPHORE);
 		if(startup->options&WEB_OPT_HTTP_LOGGING) {
@@ -5782,21 +5963,6 @@ void DLLCALL web_server(void* arg)
 			_beginthread(http_logging_thread, 0, startup->logfile_base);
 		}
 
-#ifdef ONE_JS_RUNTIME
-	    if(js_runtime == NULL) {
-    	    lprintf(LOG_DEBUG,"%04d JavaScript: Creating runtime: %lu bytes"
-        	    ,server_socket,startup->js.max_bytes);
-
-    	    if((js_runtime=jsrt_GetNew(startup->js.max_bytes, 0, __FILE__, __LINE__))==NULL) {
-        	    lprintf(LOG_ERR,"%04d !ERROR creating JavaScript runtime",server_socket);
-				/* Sleep 15 seconds then try again */
-				/* ToDo: Something better should be used here. */
-				SLEEP(15000);
-				continue;
-        	}
-    	}
-#endif
-
 		/* Setup recycle/shutdown semaphore file lists */
 		shutdown_semfiles=semfile_list_init(scfg.ctrl_dir,"shutdown","web");
 		recycle_semfiles=semfile_list_init(scfg.ctrl_dir,"recycle","web");
@@ -5815,16 +5981,15 @@ void DLLCALL web_server(void* arg)
 		if(startup->started!=NULL)
     		startup->started(startup->cbdata);
 
-		lprintf(LOG_INFO,"%04d Web Server thread started", server_socket);
+		lprintf(LOG_INFO,"Web Server thread started");
 
-		while(server_socket!=INVALID_SOCKET && !terminate_server) {
+		while(!terminated && !terminate_server) {
 
 			/* check for re-cycle/shutdown semaphores */
 			if(protected_uint32_value(thread_count) <= (2 /* web_server() and http_output_thread() */ + http_logging_thread_running)) {
 				if(!(startup->options&BBS_OPT_NO_RECYCLE)) {
 					if((p=semfile_list_check(&initialized,recycle_semfiles))!=NULL) {
-						lprintf(LOG_INFO,"%04d Recycle semaphore file (%s) detected"
-							,server_socket,p);
+						lprintf(LOG_INFO,"Recycle semaphore file (%s) detected",p);
 						if(session!=NULL) {
 							pthread_mutex_unlock(&session->struct_filled);
 							session=NULL;
@@ -5832,7 +5997,7 @@ void DLLCALL web_server(void* arg)
 						break;
 					}
 					if(startup->recycle_now==TRUE) {
-						lprintf(LOG_INFO,"%04d Recycle semaphore signaled",server_socket);
+						lprintf(LOG_INFO,"Recycle semaphore signaled");
 						startup->recycle_now=FALSE;
 						if(session!=NULL) {
 							pthread_mutex_unlock(&session->struct_filled);
@@ -5842,11 +6007,9 @@ void DLLCALL web_server(void* arg)
 					}
 				}
 				if(((p=semfile_list_check(&initialized,shutdown_semfiles))!=NULL
-						&& lprintf(LOG_INFO,"%04d Shutdown semaphore file (%s) detected"
-							,server_socket,p))
+						&& lprintf(LOG_INFO,"Shutdown semaphore file (%s) detected",p))
 					|| (startup->shutdown_now==TRUE
-						&& lprintf(LOG_INFO,"%04d Shutdown semaphore signaled"
-							,server_socket))) {
+						&& lprintf(LOG_INFO,"Shutdown semaphore signaled"))) {
 					startup->shutdown_now=FALSE;
 					terminate_server=TRUE;
 					if(session!=NULL) {
@@ -5861,9 +6024,7 @@ void DLLCALL web_server(void* arg)
 			if(session==NULL) {
 				/* FREE()d at the start of the session thread */
 				if((session=malloc(sizeof(http_session_t)))==NULL) {
-					lprintf(LOG_CRIT,"%04d !ERROR allocating %u bytes of memory for http_session_t"
-						,server_socket, sizeof(http_session_t));
-					mswait(3000);
+					lprintf(LOG_CRIT,"!ERROR allocating %u bytes of memory for http_session_t", sizeof(http_session_t));
 					continue;
 				}
 				memset(session, 0, sizeof(http_session_t));
@@ -5876,60 +6037,22 @@ void DLLCALL web_server(void* arg)
 			}
 
 			/* now wait for connection */
+			client_addr_len = sizeof(client_addr);
+			client_socket = xpms_accept(ws_set, &client_addr, &client_addr_len, startup->sem_chk_freq*1000, &acc_type);
 
-			FD_ZERO(&socket_set);
-			FD_SET(server_socket,&socket_set);
-			high_socket_set=server_socket+1;
-
-			tv.tv_sec=startup->sem_chk_freq;
-			tv.tv_usec=0;
-
-			if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) {
-				if(i==0)
-					continue;
-				if(ERROR_VALUE==EINTR)
-					lprintf(LOG_DEBUG,"Web Server listening interrupted");
-				else if(ERROR_VALUE == ENOTSOCK)
-            		lprintf(LOG_INFO,"Web Server socket closed");
-				else
-					lprintf(LOG_WARNING,"!ERROR %d selecting socket",ERROR_VALUE);
+			if(client_socket == INVALID_SOCKET)
 				continue;
-			}
 
-			if(server_socket==INVALID_SOCKET) {	/* terminated */
+			if(terminated) {	/* terminated */
 				pthread_mutex_unlock(&session->struct_filled);
 				session=NULL;
 				break;
 			}
 
-			client_addr_len = sizeof(client_addr);
-
-			if(server_socket!=INVALID_SOCKET
-				&& FD_ISSET(server_socket,&socket_set)) {
-				client_socket = accept(server_socket, (struct sockaddr *)&client_addr
-	        		,&client_addr_len);
-			}
-			else {
-				lprintf(LOG_NOTICE,"!NO SOCKETS set by select");
-				continue;
-			}
-
-			if(client_socket == INVALID_SOCKET)	{
-				lprintf(LOG_WARNING,"!ERROR %d accepting connection", ERROR_VALUE);
-#ifdef _WIN32
-				if(WSAGetLastError()==WSAENOBUFS) {	/* recycle (re-init WinSock) on this error */
-					pthread_mutex_unlock(&session->struct_filled);
-					session=NULL;
-					break;
-				}
-#endif
-				continue;
-			}
-
 			if(startup->socket_open!=NULL)
 				startup->socket_open(startup->cbdata,TRUE);
 
-			SAFECOPY(host_ip,inet_ntoa(client_addr.sin_addr));
+			inet_addrtop(&client_addr, host_ip, sizeof(host_ip));
 
 			if(trashcan(&scfg,host_ip,"ip-silent")) {
 				close_socket(&client_socket);
@@ -5939,29 +6062,31 @@ void DLLCALL web_server(void* arg)
 			if(startup->max_clients && protected_uint32_value(active_clients)>=startup->max_clients) {
 				lprintf(LOG_WARNING,"%04d !MAXIMUM CLIENTS (%d) reached, access denied"
 					,client_socket, startup->max_clients);
-				mswait(3000);
+				if (!len_503)
+					len_503 = strlen(error_503);
+				sendsocket(client_socket, error_503, len_503);
 				close_socket(&client_socket);
 				continue;
-			}
+            }
 
-			host_port=ntohs(client_addr.sin_port);
+			host_port=inet_addrport(&client_addr);
 
-			lprintf(LOG_INFO,"%04d HTTP connection accepted from: %s port %u"
+			if (acc_type != NULL && !strcmp(acc_type, "TLS"))
+				session->is_tls=TRUE;
+			lprintf(LOG_INFO,"%04d %s connection accepted from: %s port %u"
 				,client_socket
+				,session->is_tls ? "HTTP":"HTTPS"
 				,host_ip, host_port);
 
 			SAFECOPY(session->host_ip,host_ip);
-			session->addr=client_addr;
+			memcpy(&session->addr, &client_addr, sizeof(session->addr));
+			session->addr_len=client_addr_len;
    			session->socket=client_socket;
 			session->js_callback.auto_terminate=TRUE;
 			session->js_callback.terminated=&terminate_server;
 			session->js_callback.limit=startup->js.time_limit;
 			session->js_callback.gc_interval=startup->js.gc_interval;
 			session->js_callback.yield_interval=startup->js.yield_interval;
-#ifdef ONE_JS_RUNTIME
-			session->js_runtime=js_runtime;
-#endif
-
 			pthread_mutex_unlock(&session->struct_filled);
 			session=NULL;
 			served++;
@@ -5974,13 +6099,13 @@ void DLLCALL web_server(void* arg)
 
 		/* Wait for active clients to terminate */
 		if(protected_uint32_value(active_clients)) {
-			lprintf(LOG_DEBUG,"%04d Waiting for %d active clients to disconnect..."
-				,server_socket, protected_uint32_value(active_clients));
+			lprintf(LOG_DEBUG,"Waiting for %d active clients to disconnect..."
+				, protected_uint32_value(active_clients));
 			start=time(NULL);
 			while(protected_uint32_value(active_clients)) {
 				if(time(NULL)-start>startup->max_inactivity) {
-					lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for %d active clients"
-						,server_socket, protected_uint32_value(active_clients));
+					lprintf(LOG_WARNING,"!TIMEOUT waiting for %d active clients"
+						, protected_uint32_value(active_clients));
 					break;
 				}
 				mswait(100);
@@ -5993,27 +6118,18 @@ void DLLCALL web_server(void* arg)
 			mswait(100);
 		}
 		if(http_logging_thread_running) {
-			lprintf(LOG_DEBUG,"%04d Waiting for HTTP logging thread to terminate..."
-				,server_socket);
+			lprintf(LOG_DEBUG,"Waiting for HTTP logging thread to terminate...");
 			start=time(NULL);
 			while(http_logging_thread_running) {
 				if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
-					lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for HTTP logging thread to "
-            			"terminate", server_socket);
+					lprintf(LOG_WARNING,"!TIMEOUT waiting for HTTP logging thread to "
+            			"terminate");
 					break;
 				}
 				mswait(100);
 			}
 		}
 
-#ifdef ONE_JS_RUNTIME
-    	if(js_runtime!=NULL) {
-        	lprintf(LOG_DEBUG,"%04d JavaScript: Destroying runtime",server_socket);
-        	jsrt_Release(js_runtime);
-    	    js_runtime=NULL;
-	    }
-#endif
-
 		cleanup(0);
 
 		if(!terminate_server) {
diff --git a/src/sbbs3/websrvr.h b/src/sbbs3/websrvr.h
index e089e219325897bf7ac8be064a4db0f7823c51f8..7d38fffddb48d1fab17393993438bd219cf5ce2d 100644
--- a/src/sbbs3/websrvr.h
+++ b/src/sbbs3/websrvr.h
@@ -43,17 +43,21 @@
 #include "semwrap.h"			/* sem_t */
 
 typedef struct {
-	DWORD	size;				/* sizeof(web_startup_t) */
-	WORD	port;
-	WORD	max_clients;
+	DWORD		size;				/* sizeof(web_startup_t) */
+	WORD		max_clients;
 #define WEB_DEFAULT_MAX_CLIENTS			0	/* 0=unlimited */
-	WORD	max_inactivity;
+	WORD		max_inactivity;
 #define WEB_DEFAULT_MAX_INACTIVITY		120	/* seconds */
-	WORD	max_cgi_inactivity;
+	WORD		max_cgi_inactivity;
 #define WEB_DEFAULT_MAX_CGI_INACTIVITY	120	/* seconds */
-	WORD	sem_chk_freq;		/* semaphore file checking frequency (in seconds) */
-    DWORD   interface_addr;
-    DWORD	options;
+	WORD		sem_chk_freq;		/* semaphore file checking frequency (in seconds) */
+    DWORD		options;
+	WORD		port;
+	WORD		tls_port;
+	struct in_addr outgoing4;
+	struct in6_addr	outgoing6;
+    str_list_t	interfaces;
+    str_list_t	tls_interfaces;
 	
 	void*	cbdata;				/* Private data passed to callbacks */ 
 
@@ -114,7 +118,9 @@ typedef struct {
 /* startup options that requires re-initialization/recycle when changed */
 static struct init_field web_init_fields[] = { 
 	 OFFSET_AND_SIZE(web_startup_t,port)
-	,OFFSET_AND_SIZE(web_startup_t,interface_addr)
+	,OFFSET_AND_SIZE(web_startup_t,interfaces)
+	,OFFSET_AND_SIZE(web_startup_t,outgoing4)
+	,OFFSET_AND_SIZE(web_startup_t,outgoing6)
 	,OFFSET_AND_SIZE(web_startup_t,ctrl_dir)
 	,OFFSET_AND_SIZE(web_startup_t,root_dir)
 	,OFFSET_AND_SIZE(web_startup_t,error_dir)
@@ -159,7 +165,7 @@ static ini_bitdesc_t web_options[] = {
 #define WEB_DEFAULT_ROOT_DIR		"../web/root"
 #define WEB_DEFAULT_ERROR_DIR		"error"
 #define WEB_DEFAULT_CGI_DIR			"cgi-bin"
-#define WEB_DEFAULT_AUTH_LIST		"Basic,Digest"
+#define WEB_DEFAULT_AUTH_LIST		"Basic,Digest,TLS-PSK"
 #define WEB_DEFAULT_CGI_CONTENT		"text/plain"
 
 #ifdef DLLEXPORT