diff --git a/ctrl/text.dat b/ctrl/text.dat
index 6c14523244d973dca5ed13a31a59773bddd82154..5699f8a9aea973aeaee6a35b24426ebf12610fda 100644
--- a/ctrl/text.dat
+++ b/ctrl/text.dat
@@ -50,9 +50,9 @@
 "\1[\1n\1mPosted on \1h%s\1n\1m %s.\r\n"                      037 Posted
 "\7\1_\1w\1hNode %2d: \1g%s\1n\1g sent you E-mail.\r\n"       038 EmailNodeMsg
 "\1n\r\nYou can't forward mail.\r\n"                     039 R_Forward
-"\1n\1m\r\nForwarded by \1h%s\1n\1m on "\                    040 ForwardedFrom
+"\1n\1mForwarded by \1h%s\1n\1m on "\                    040 ForwardedFrom
 	"\1h%s\1n\r\n"
-"\1n\1m\r\nMail forwarded to \1h%s \1n\1m#%d.\1n\r\n"         041 Forwarded
+"\1n\1m\r\nMail forwarded to \1h%s\1n\r\n"                    041 Forwarded
 "\1b\1hAuto message by: \1c%s\1b on %s\1n\r\n\r\n"            042 AutoMsgBy
 "\r\nAuto Message - ~Read, ~Write, or ~Quit: "          043 AutoMsg
 "\1n\r\nYou can't write to the auto-message.\r\n"        044 R_AutoMsg
diff --git a/exec/emailval.js b/exec/emailval.js
index d04927377864fcabc8ef6623b282ac04d2023e73..b9f368bf6b629bd1188e75ddca86839cf0406ea5 100644
--- a/exec/emailval.js
+++ b/exec/emailval.js
@@ -1,4 +1,4 @@
-// $Id: emailval.js,v 1.7 2019/07/15 04:41:35 rswindell Exp $
+
 /*******************************************************************************
 Originally based on:
 FILE: emailval.js v0.2
@@ -21,7 +21,7 @@ STEP 2:
 Edit ctrl/modopts.ini in a text editor and edit the following values in the
 [emailval] section (create if it necessary) to match your pre-validation and
 post-validation security levels.
-        level_before_validation (default: 50)
+		level_before_validation (default: 50)
 		level_after_validation (default: 60)
 		flags1_after_validation (default: no change)
 		flags2_after_validation (default: no change)
@@ -31,6 +31,8 @@ post-validation security levels.
 		restrictions_after_validation (default: no change)
 		expiration_after_validation (default: false)
 		expiration_days_after_validation (default: no change)
+		valid_chars=ACDEFHJKLMNPQRTUVWXY23456789!@#$%&*
+		code_length=16
 
 Note: the flags, exemptions, and restrictions .ini values support 'A' through
       'Z' with the optional '+' (add) and '-' (remove) modifiers.
@@ -65,9 +67,10 @@ if(options.level_after_validation === undefined)
 	options.level_after_validation = 60;
 
 //other constants, shouldn't need changing.
-var cValChars='ACDEFHJKLMNPQRTUVWXY23456789!@#$%&*';
+var cValChars = options.valid_chars !== undefined ? options.valid_chars :
+    'ACDEFHJKLMNPQRTUVWXY23456789!@#$%&*';
 var cPrevalText = "telvalcode";
-var cValCodeLen = 16;
+var cValCodeLen = options.code_length !== undefined ? options.code_length : 16;
 
 //include SBBS Definition constants
 require("sbbsdefs.js", 'NET_NONE'); 
@@ -214,4 +217,4 @@ function CheckValidation() {
 		}
 	}
 }
-CheckValidation();
\ No newline at end of file
+CheckValidation();
diff --git a/exec/init-fidonet.ini b/exec/init-fidonet.ini
index 0591376899a7cad042950b37ba3d27a2aaaca894..7c48a208e9915cb6c959f9491de9df5cb2c9b716 100644
--- a/exec/init-fidonet.ini
+++ b/exec/init-fidonet.ini
@@ -98,6 +98,16 @@ fido  = 1:19/37
 host  = hub.cybernetbbs.net
 areatag_prefix = CN_
 
+[zone:42]
+name = SFNet
+desc = Science Fiction
+pack = http://furmenservices.net/sfnet.zip
+coord = Dallas Vinson
+addr = 42:1/1
+host = furmenservices.net
+echolist = sfnet.na
+areatag_prefix = SF_
+
 [zone:44]
 name  = DoRENET
 desc  = BBS modifications, coding, ansi/asci etc
diff --git a/src/sbbs3/.gitignore b/src/sbbs3/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..3467fa0e2eb31f7f349642e563cf3509f4a93a6c
--- /dev/null
+++ b/src/sbbs3/.gitignore
@@ -0,0 +1,2 @@
+git_branch.h
+git_hash.h
diff --git a/src/sbbs3/atcodes.cpp b/src/sbbs3/atcodes.cpp
index 7cae16039d2e3511b7949b23def0a34a1f694dd5..f4384eaea74f410c31c4a93c25781a4af9b336f1 100644
--- a/src/sbbs3/atcodes.cpp
+++ b/src/sbbs3/atcodes.cpp
@@ -1,7 +1,4 @@
 /* Synchronet "@code" functions */
-// vi: tabstop=4
-
-/* $Id: atcodes.cpp,v 1.142 2020/05/10 20:12:35 rswindell Exp $ */
 
 /****************************************************************************
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
@@ -16,21 +13,9 @@
  * See the GNU General Public License for more details: gpl.txt or			*
  * http://www.fsf.org/copyleft/gpl.html										*
  *																			*
- * Anonymous FTP access to the most recent released source is available at	*
- * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
- *																			*
- * Anonymous CVS access to the development source and modification history	*
- * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
- * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
- *     (just hit return, no password is necessary)							*
- * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
- *																			*
  * For Synchronet coding style and modification guidelines, see				*
  * http://www.synchro.net/source.html										*
  *																			*
- * You are encouraged to submit any modifications (preferably in Unix diff	*
- * format) via e-mail to mods@synchro.net									*
- *																			*
  * Note: If this box doesn't appear square, then you need to fix your tabs.	*
  ****************************************************************************/
 
@@ -39,6 +24,7 @@
 #include "utf8.h"
 #include "unicode.h"
 #include "cp437defs.h"
+#include "ver.h"
 
 #if defined(_WINSOCKAPI_)
 	extern WSADATA WSAData;
@@ -411,6 +397,12 @@ const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool
 		return(str);
 	}
 
+	if(strcmp(sp, "GIT_HASH") == 0)
+		return git_hash;
+
+	if(strcmp(sp, "GIT_BRANCH") == 0)
+		return git_branch;
+
 	if(!strcmp(sp,"UPTIME")) {
 		extern volatile time_t uptime;
 		time_t up=0;
diff --git a/src/sbbs3/con_out.cpp b/src/sbbs3/con_out.cpp
index 0063c660b578461103ef0190c0d8206dd5b0625c..b0e05b9f9988bee062f7870666ca33387c2c5c6d 100644
--- a/src/sbbs3/con_out.cpp
+++ b/src/sbbs3/con_out.cpp
@@ -787,7 +787,7 @@ void sbbs_t::inc_row(int count)
 	}
 }
 
-void sbbs_t::center(char *instr, unsigned int columns)
+void sbbs_t::center(const char *instr, unsigned int columns)
 {
 	char str[256];
 	size_t len;
diff --git a/src/sbbs3/getmsg.cpp b/src/sbbs3/getmsg.cpp
index 05c00703c6ab146591f05caa1a7aa166dcff45a3..d0c98915b5a366126d99ca131587b1f8c57d4752 100644
--- a/src/sbbs3/getmsg.cpp
+++ b/src/sbbs3/getmsg.cpp
@@ -272,19 +272,19 @@ bool sbbs_t::show_msg(smb_t* smb, smbmsg_t* msg, long p_mode, post_t* post)
 
 	show_msghdr(smb, msg);
 
+	int comments=0;
+	for(int i = 0; i < msg->total_hfields; i++)
+		if(msg->hfield[i].type == SMB_COMMENT) {
+			bprintf("%s\r\n", (char*)msg->hfield_dat[i]);
+			comments++;
+		}
+	if(comments)
+		CRLF;
+
 	if(msg->hdr.type == SMB_MSG_TYPE_POLL && post != NULL && smb->subnum < cfg.total_subs) {
 		char* answer;
 		int longest_answer = 0;
 
-		int comments=0;
-		for(int i = 0; i < msg->total_hfields; i++)
-			if(msg->hfield[i].type == SMB_COMMENT) {
-				bprintf("%s\r\n", (char*)msg->hfield_dat[i]);
-				comments++;
-			}
-		if(comments)
-			CRLF;
-
 		for(int i = 0; i < msg->total_hfields; i++) {
 			if(msg->hfield[i].type != SMB_POLL_ANSWER)
 				continue;
@@ -332,7 +332,7 @@ bool sbbs_t::show_msg(smb_t* smb, smbmsg_t* msg, long p_mode, post_t* post)
 			mnemonics(text[VoteInThisPollNow]);
 		return true;
 	}
-	if((txt=smb_getmsgtxt(smb, msg, 0)) == NULL)
+	if((txt=smb_getmsgtxt(smb, msg, GETMSGTXT_BODY_ONLY)) == NULL)
 		return false;
 	char* p = txt;
 	if(!(console&CON_RAW_IN)) {
diff --git a/src/sbbs3/gitinfo.bat b/src/sbbs3/gitinfo.bat
new file mode 100644
index 0000000000000000000000000000000000000000..63b9bec24281ecdfdae45c7e7155cf4a95a10f91
--- /dev/null
+++ b/src/sbbs3/gitinfo.bat
@@ -0,0 +1,4 @@
+@git log -1 HEAD --format="#define GIT_HASH \"%%h\"" > git_hash.h
+@echo #define GIT_BRANCH ^"| tr -d "\r\n" > git_branch.h
+@git rev-parse --abbrev-ref HEAD | tr -d "\n" >> git_branch.h
+@echo ^" >> git_branch.h
\ No newline at end of file
diff --git a/src/sbbs3/js_system.c b/src/sbbs3/js_system.c
index 4d5b17b5c9476ab3795202f419e8ef6d6d632e86..bd7039212ae503fa3b89a0a971a6b2a175b6785e 100644
--- a/src/sbbs3/js_system.c
+++ b/src/sbbs3/js_system.c
@@ -21,6 +21,7 @@
 
 #include "sbbs.h"
 #include "js_request.h"
+#include "ver.h"
 
 #ifdef JAVASCRIPT
 
@@ -529,6 +530,8 @@ static char* sys_prop_desc[] = {
 	,"Synchronet version notice (includes version and platform)"
 	,"Synchronet version number in decimal (e.g. 31301 for v3.13b)"
 	,"Synchronet version number in hexadecimal (e.g. 0x31301 for v3.13b)"
+	,"Synchronet Git repository branch name"
+	,"Synchronet Git repository commit hash"
 	,"platform description (e.g. 'Win32', 'Linux', 'FreeBSD')"
 	,"architecture description (e.g. 'i386', 'i686', 'x86_64')"
 	,"message base library version information"
@@ -2475,6 +2478,10 @@ static JSBool js_system_resolve(JSContext *cx, JSObject *obj, jsid id)
 	LAZY_INTEGER("version_num", VERSION_NUM);
 	LAZY_INTEGER("version_hex", VERSION_HEX);
 
+	/* Git repo details */
+	LAZY_STRING("git_branch", git_branch);
+	LAZY_STRING("git_hash", git_hash);
+
 	LAZY_STRING("platform", PLATFORM_DESC);
 	LAZY_STRING("architecture", ARCHITECTURE_DESC);
 	LAZY_STRFUNC("msgbase_lib", sprintf(str,"SMBLIB %s",smb_lib_ver()), str);
diff --git a/src/sbbs3/msg_id.c b/src/sbbs3/msg_id.c
index 26a5ad216f71fae427d8f7a1c3eeb90356dd9702..e9db6310d792421f0aabd9bb1378d22e2ec7f07e 100644
--- a/src/sbbs3/msg_id.c
+++ b/src/sbbs3/msg_id.c
@@ -21,6 +21,8 @@
 
 #include "msg_id.h"
 #include "smblib.h"
+#include "git_branch.h"
+#include "git_hash.h"
 
 static ulong msg_number(smbmsg_t* msg)
 {
@@ -242,8 +244,9 @@ char* DLLCALL msg_program_id(char* pid, size_t maxlen)
 	char compiler[64];
 
 	DESCRIBE_COMPILER(compiler);
-	snprintf(pid, maxlen, "%.10s %s%c-%s  %s %s"
+	snprintf(pid, maxlen, "%.10s %s%c-%s %s/%s %s %s"
 		,VERSION_NOTICE,VERSION,REVISION,PLATFORM_DESC
+		,GIT_BRANCH, GIT_HASH
 		,__DATE__,compiler);
 	return pid;
 }
diff --git a/src/sbbs3/netmail.cpp b/src/sbbs3/netmail.cpp
index eab9d211cddb2a309b1e3123f133de1841ad7a84..457ca30eaa02980ff84a712be10c3b0bf248d2c4 100644
--- a/src/sbbs3/netmail.cpp
+++ b/src/sbbs3/netmail.cpp
@@ -480,7 +480,7 @@ void sbbs_t::qwktonetmail(FILE *rep, char *block, char *into, uchar fromhub)
 				l+=strlen(str)+1;
 				cp=str;
 				while(*cp && *cp<=' ') cp++;
-				sprintf(senderaddr,"%s/%s",sender_id,cp);
+				safe_snprintf(senderaddr, sizeof(senderaddr), "%s/%s",sender_id,cp);
 				strupr(senderaddr);
 				smb_hfield(&msg,SENDERNETADDR,strlen(senderaddr),senderaddr); 
 			}
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index 1ecab93af29fced178ece415fdb750e8cedae7c8..f12b8945a5999e4f2eb26f0d5ed7743e1b2af428 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -681,7 +681,7 @@ public:
 	bool	msgabort(void);
 	bool	email(int usernumber, const char *top = NULL, const char *title = NULL
 				, long mode = WM_NONE, smb_t* resmb = NULL, smbmsg_t* remsg = NULL);
-	bool	forwardmail(smbmsg_t* msg, const char* to);
+	bool	forwardmail(smbmsg_t* msg, const char* to, const char* subject = NULL, const char* comment = NULL);
 	void	removeline(char *str, char *str2, char num, char skip);
 	ulong	msgeditor(char *buf, const char *top, char *title);
 	bool	editfile(char *path, bool msg=false);
@@ -690,7 +690,7 @@ public:
 	void	editmsg(smbmsg_t* msg, uint subnum);
 	void	editor_inf(int xeditnum, const char *to, const char* from, const char *subj, long mode
 				,uint subnum, const char* tagfile);
-	void	copyfattach(uint to, uint from, char *title);
+	bool	copyfattach(uint to, uint from, const char* subj);
 	bool	movemsg(smbmsg_t* msg, uint subnum);
 	int		process_edited_text(char* buf, FILE* stream, long mode, unsigned* lines, unsigned maxlines);
 	int		process_edited_file(const char* src, const char* dest, long mode, unsigned* lines, unsigned maxlines);
@@ -759,7 +759,7 @@ public:
 	int		outchar(enum unicode_codepoint, const char* cp437_fallback = NULL);
 	void	inc_row(int count);
 	void	inc_column(int count);
-	void	center(char *str, unsigned int columns = 0);
+	void	center(const char *str, unsigned int columns = 0);
 	void	wide(const char*);
 	void	clearscreen(long term);
 	void	clearline(void);
@@ -1439,12 +1439,6 @@ extern char lastuseron[LEN_ALIAS+1];  /* Name of user last online */
 }
 #endif
 
-extern
-#ifdef __cplusplus
- "C"
-#endif
-	const char* beta_version;
-
 /* Global data */
 
 /* ToDo: These should be hunted down and killed */
diff --git a/src/sbbs3/sbbs.vcxproj b/src/sbbs3/sbbs.vcxproj
index e2f04731b4d8d16f830318bcecaaf25e379b4b9f..d71e501bee654dd794ee3e3d579668f54195658f 100644
--- a/src/sbbs3/sbbs.vcxproj
+++ b/src/sbbs3/sbbs.vcxproj
@@ -114,6 +114,9 @@
       <SuppressStartupBanner>true</SuppressStartupBanner>
       <OutputFile>.\msvc.win32.dll.debug/sbbs.bsc</OutputFile>
     </Bscmake>
+    <PreBuildEvent>
+      <Command>gitinfo.bat</Command>
+    </PreBuildEvent>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <Midl>
@@ -163,6 +166,9 @@
       <SuppressStartupBanner>true</SuppressStartupBanner>
       <OutputFile>.\msvc.win32.dll.release/sbbs.bsc</OutputFile>
     </Bscmake>
+    <PreBuildEvent>
+      <Command>gitinfo.bat</Command>
+    </PreBuildEvent>
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\comio\comio.c" />
diff --git a/src/sbbs3/sbbsecho.c b/src/sbbs3/sbbsecho.c
index 83ee49abc685ba263123500d9c0383467c5180d7..180e9ddb2a71adf21b7047d61e35ec217164609e 100644
--- a/src/sbbs3/sbbsecho.c
+++ b/src/sbbs3/sbbsecho.c
@@ -1262,10 +1262,6 @@ int create_netmail(const char *to, const smbmsg_t* msg, const char *subject, con
 		fprintf(fp, "\1CHRS: %s\r", charset);
 		if(msg->editor != NULL)
 			fprintf(fp, "\1NOTE: %s\r", msg->editor);
-		/* comment headers are part of text */
-		for(i=0; i<msg->total_hfields; i++)
-			if(msg->hfield[i].type == SMB_COMMENT)
-				fprintf(fp, "%s\r", (char*)msg->hfield_dat[i]);
 		if(subject != msg->subj)
 			fprintf(fp, "Subject: %s\r\r", msg->subj);
 	}
@@ -5142,7 +5138,7 @@ bool retoss_bad_echomail(void)
 			continue;
 		}
 
-		char* body = smb_getmsgtxt(&badsmb, &badmsg, GETMSGTXT_BODY_ONLY);
+		char* body = smb_getmsgtxt(&badsmb, &badmsg, GETMSGTXT_NO_TAILS);
 		if(body == NULL) {
 			smb_unlockmsghdr(&badsmb,&badmsg);
 			smb_freemsgmem(&badmsg);
diff --git a/src/sbbs3/scfg/scfgsub.c b/src/sbbs3/scfg/scfgsub.c
index dcde35ea06a2310c7605052005a6236795fe493f..5e14e49d0c6072b8ec26171cfe8adca09343e222 100644
--- a/src/sbbs3/scfg/scfgsub.c
+++ b/src/sbbs3/scfg/scfgsub.c
@@ -1373,8 +1373,10 @@ void sub_cfg(uint grpnum)
 								}
 								opt[n][0]=0;
 								n = uifc.list(WIN_RHT|WIN_SAV|WIN_ACT|WIN_INSACT, 0, 0, 0, &k, NULL, "FidoNet Address", opt);
-								if(n >= 0 && n < cfg.total_faddrs)
+								if(n >= 0 && n < cfg.total_faddrs) {
 									cfg.sub[i]->faddr = cfg.faddr[n];
+									uifc.changes = TRUE;
+								}
 								break;
 							}
 							case 8:
diff --git a/src/sbbs3/targets.mk b/src/sbbs3/targets.mk
index 7453e7044f3f18e1d7c9f90370bc645b26eb73c4..51738ea0d23edcddcf59b149677a39d73550d5ce 100644
--- a/src/sbbs3/targets.mk
+++ b/src/sbbs3/targets.mk
@@ -51,7 +51,9 @@ UTILS		= $(FIXSMB) $(CHKSMB) \
 			  $(SEXYZ) $(DSTSEDIT) $(READSAUCE) $(SHOWSTAT) \
 			  $(PKTDUMP) $(FMSGDUMP)
 
-all:	dlls utils console scfg uedit umonitor
+GIT_INFO	= git_hash.h git_branch.h
+
+all:	$(GIT_INFO) dlls utils console scfg uedit umonitor
 
 console:	$(JS_DEPS) xpdev-mt smblib \
 		$(MTOBJODIR) $(LIBODIR) $(EXEODIR) \
@@ -114,6 +116,17 @@ symlinks: all
 	ln -sfr */$(EXEODIR)/* $(SBBSEXEC)
 endif
 
+.PHONY: FORCE
+FORCE:
+
+ifneq ($(GIT), NO)
+git_hash.h: FORCE ../../.git
+	echo '#define GIT_HASH "'`git log -1 HEAD --format=%h`\" > $@
+
+git_branch.h: FORCE ../../.git
+	echo '#define GIT_BRANCH "'`git rev-parse --abbrev-ref HEAD`\" > $@
+endif
+
 ifeq ($(os),linux)
 .PHONY: setcap
 setcap: all
@@ -124,10 +137,10 @@ endif
 sexyz:	$(SEXYZ)
 
 .PHONY: jsdoor
-jsdoor: $(JS_DEPS) $(CRYPT_DEPS) $(XPDEV-MT_LIB) $(SMBLIB) $(UIFCLIB-MT) $(CIOLIB-MT) $(JSDOOR)
+jsdoor: $(GIT_INFO) $(JS_DEPS) $(CRYPT_DEPS) $(XPDEV-MT_LIB) $(SMBLIB) $(UIFCLIB-MT) $(CIOLIB-MT) $(JSDOOR)
 
 # Library dependencies
-$(SBBS): 
+$(SBBS):
 $(FTPSRVR): 
 $(WEBSRVR):
 $(MAILSRVR):
diff --git a/src/sbbs3/text_defaults.c b/src/sbbs3/text_defaults.c
index af7b5f1f0ee197272501e14a883df924fe3921b3..b6e55e09d86297de0ff613fd319639638f0a3ef2 100644
--- a/src/sbbs3/text_defaults.c
+++ b/src/sbbs3/text_defaults.c
@@ -66,10 +66,10 @@ const char * const text_defaults[TOTAL_TEXT]={
 	,"\x07\x01\x5f\x01\x77\x01\x68\x4e\x6f\x64\x65\x20\x25\x32\x64\x3a\x20\x01\x67\x25\x73\x01\x6e\x01\x67\x20\x73\x65\x6e\x74\x20\x79"
 		"\x6f\x75\x20\x45\x2d\x6d\x61\x69\x6c\x2e\x0d\x0a" // 038 EmailNodeMsg
 	,"\x01\x6e\x0d\x0a\x59\x6f\x75\x20\x63\x61\x6e\x27\x74\x20\x66\x6f\x72\x77\x61\x72\x64\x20\x6d\x61\x69\x6c\x2e\x0d\x0a" // 039 R_Forward
-	,"\x01\x6e\x01\x6d\x0d\x0a\x46\x6f\x72\x77\x61\x72\x64\x65\x64\x20\x62\x79\x20\x01\x68\x25\x73\x01\x6e\x01\x6d\x20\x6f\x6e\x20\x01"
-		"\x68\x25\x73\x01\x6e\x0d\x0a" // 040 ForwardedFrom
-	,"\x01\x6e\x01\x6d\x0d\x0a\x4d\x61\x69\x6c\x20\x66\x6f\x72\x77\x61\x72\x64\x65\x64\x20\x74\x6f\x20\x01\x68\x25\x73\x20\x01\x6e\x01"
-		"\x6d\x23\x25\x64\x2e\x01\x6e\x0d\x0a" // 041 Forwarded
+	,"\x01\x6e\x01\x6d\x46\x6f\x72\x77\x61\x72\x64\x65\x64\x20\x62\x79\x20\x01\x68\x25\x73\x01\x6e\x01\x6d\x20\x6f\x6e\x20\x01\x68\x25"
+		"\x73\x01\x6e\x0d\x0a" // 040 ForwardedFrom
+	,"\x01\x6e\x01\x6d\x0d\x0a\x4d\x61\x69\x6c\x20\x66\x6f\x72\x77\x61\x72\x64\x65\x64\x20\x74\x6f\x20\x01\x68\x25\x73\x01\x6e\x0d\x0a"
+		"" // 041 Forwarded
 	,"\x01\x62\x01\x68\x41\x75\x74\x6f\x20\x6d\x65\x73\x73\x61\x67\x65\x20\x62\x79\x3a\x20\x01\x63\x25\x73\x01\x62\x20\x6f\x6e\x20\x25"
 		"\x73\x01\x6e\x0d\x0a\x0d\x0a" // 042 AutoMsgBy
 	,"\x0d\x0a\x41\x75\x74\x6f\x20\x4d\x65\x73\x73\x61\x67\x65\x20\x2d\x20\x7e\x52\x65\x61\x64\x2c\x20\x7e\x57\x72\x69\x74\x65\x2c\x20"
diff --git a/src/sbbs3/ver.cpp b/src/sbbs3/ver.cpp
index bc6e5d753c9cc6166dabdf5a0da1a7bb30d89b8e..ac196890851021b69e83f7836692e70310b1dc15 100644
--- a/src/sbbs3/ver.cpp
+++ b/src/sbbs3/ver.cpp
@@ -1,9 +1,4 @@
-/* ver.cpp */
-// vi: tabstop=4
-
-/* Synchronet version display */
-
-/* $Id: ver.cpp,v 1.31 2019/10/08 02:07:26 rswindell Exp $ */
+/* Synchronet version info */
 
 /****************************************************************************
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
@@ -18,28 +13,21 @@
  * See the GNU General Public License for more details: gpl.txt or			*
  * http://www.fsf.org/copyleft/gpl.html										*
  *																			*
- * Anonymous FTP access to the most recent released source is available at	*
- * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
- *																			*
- * Anonymous CVS access to the development source and modification history	*
- * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
- * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
- *     (just hit return, no password is necessary)							*
- * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
- *																			*
  * For Synchronet coding style and modification guidelines, see				*
  * http://www.synchro.net/source.html										*
  *																			*
- * You are encouraged to submit any modifications (preferably in Unix diff	*
- * format) via e-mail to mods@synchro.net									*
- *																			*
  * Note: If this box doesn't appear square, then you need to fix your tabs.	*
  ****************************************************************************/
 
 #include "sbbs.h"
 #include "ssl.h"
+#include "git_hash.h"
+#include "git_branch.h"
+#include "ver.h"
 
-const char* beta_version = " "; /* Space if non-beta, " beta" otherwise */
+extern "C" const char* git_hash = GIT_HASH;
+extern "C" const char* git_branch = GIT_BRANCH;
+extern "C" const char* beta_version = " "; /* Space if non-beta, " beta" otherwise */
 
 #if defined(_WINSOCKAPI_)
 	extern WSADATA WSAData;
@@ -96,7 +84,10 @@ void sbbs_t::ver()
 	center(str);
 	CRLF;
 
-	sprintf(str,"%s - http://www.synchro.net", COPYRIGHT_NOTICE);
+	center("https://gitlab.synchro.net - " GIT_BRANCH " " GIT_HASH); 
+	CRLF;
+
+	sprintf(str,"%s - http://synchro.net", COPYRIGHT_NOTICE);
 	center(str);
 	CRLF;
 
diff --git a/src/sbbs3/ver.h b/src/sbbs3/ver.h
new file mode 100644
index 0000000000000000000000000000000000000000..288605929394d995049c524bd3b57766f985e89b
--- /dev/null
+++ b/src/sbbs3/ver.h
@@ -0,0 +1,36 @@
+/* Synchronet version info */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
+ *																			*
+ * This program is free software; you can redistribute it and/or			*
+ * modify it under the terms of the GNU General Public License				*
+ * as published by the Free Software Foundation; either version 2			*
+ * of the License, or (at your option) any later version.					*
+ * See the GNU General Public License for more details: gpl.txt or			*
+ * http://www.fsf.org/copyleft/gpl.html										*
+ *																			*
+ * For Synchronet coding style and modification guidelines, see				*
+ * http://www.synchro.net/source.html										*
+ *																			*
+ * Note: If this box doesn't appear square, then you need to fix your tabs.	*
+ ****************************************************************************/
+
+#ifndef _VER_H_
+#define _VER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const char* git_hash;
+extern const char* git_branch;
+extern const char* beta_version;
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* Don't add anything after this line */
\ No newline at end of file
diff --git a/src/sbbs3/writemsg.cpp b/src/sbbs3/writemsg.cpp
index b3c763e90cc10f3e105817c2055720e0a4bdf783..189f32109ccafce6de8cc97ac68b63a049c61150 100644
--- a/src/sbbs3/writemsg.cpp
+++ b/src/sbbs3/writemsg.cpp
@@ -743,11 +743,8 @@ bool sbbs_t::writemsg(const char *fname, const char *top, char *subj, long mode,
 
 void sbbs_t::editor_info_to_msg(smbmsg_t* msg, const char* editor, const char* charset)
 {
-	if(editor != NULL)
-		smb_hfield_str(msg, SMB_EDITOR, editor);
-
-	if(charset != NULL)
-		smb_hfield_str(msg, FIDOCHARSET, charset);
+	smb_hfield_string(msg, SMB_EDITOR, editor);
+	smb_hfield_string(msg, FIDOCHARSET, charset);
 
 	ushort useron_xedit = useron.xedit;
 
@@ -1297,11 +1294,12 @@ bool sbbs_t::editfile(char *fname, bool msg)
 /*************************/
 /* Copy file attachments */
 /*************************/
-void sbbs_t::copyfattach(uint to, uint from, char *title)
+bool sbbs_t::copyfattach(uint to, uint from, const char* subj)
 {
 	char str[128],str2[128],str3[128],*tp,*sp,*p;
+	bool result = false;
 
-	strcpy(str,title);
+	strcpy(str, subj);
 	tp=str;
 	while(1) {
 		p=strchr(tp,' ');
@@ -1313,38 +1311,43 @@ void sbbs_t::copyfattach(uint to, uint from, char *title)
 			,cfg.data_dir,to,tp);
 		SAFEPRINTF3(str3,"%sfile/%04u.in/%s"  /* str2 is path/fname */
 			,cfg.data_dir,from,tp);
-		if(strcmp(str2,str3))
-			mv(str3,str2,1);
+		if(strcmp(str2,str3)) {
+			if(mv(str3, str2, /* copy */true) != 0)
+				return false;
+			result = true;
+		}
 		if(!p)
 			break;
 		tp=p+1; 
 	}
+	return result;
 }
 
-
 /****************************************************************************/
-/* Forwards mail 'msg' to 'to'												*/
+/* Forwards mail 'orgmsg' to 'to' with optional 'comment'.					*/
+/* If comment is NULL, comment lines will be prompted for.					*/
+/* If comment is a zero-length string, no comments will be included.		*/
 /****************************************************************************/
-bool sbbs_t::forwardmail(smbmsg_t* msg, const char* to)
+bool sbbs_t::forwardmail(smbmsg_t* orgmsg, const char* to, const char* subject, const char* comment)
 {
 	char		str[256],touser[128];
 	char 		tmp[512];
-	int			i;
+	char		subj[LEN_TITLE + 1];
+	int			result;
+	smbmsg_t	msg;
 	node_t		node;
-	msghdr_t	hdr=msg->hdr;
-	idxrec_t	idx=msg->idx;
-	time32_t	now32;
 	uint usernumber = 0;
 
 	if(to == NULL)
 		return false;
 
-	uint16_t net_type = smb_netaddr_type(to);
-	if(net_type == NET_NONE || net_type == NET_UNKNOWN) {
+	uint16_t net_type = NET_NONE;
+	if(strchr(to, '@') != NULL)
+		net_type = smb_netaddr_type(to);
+	if(net_type == NET_NONE) {
 		usernumber = finduser(to);
 		if(usernumber < 1)
 			return false;
-		net_type = NET_NONE;
 	} else if(!is_supported_netmail_addr(&cfg, to)) {
 		bprintf(text[InvalidNetMailAddr], to);
 		return false;
@@ -1367,64 +1370,160 @@ bool sbbs_t::forwardmail(smbmsg_t* msg, const char* to)
 		return false;
 	}
 
-	msg->idx.attr&=~(MSG_READ|MSG_DELETE);
-	msg->hdr.attr=msg->idx.attr;
+	if(subject == NULL) {
+		subject = subj;
+		SAFEPRINTF(subj, "Fwd: %s", orgmsg->subj);
+		bputs(text[SubjectPrompt]);
+		if(!getstr(subj, sizeof(subj) - 1, K_LINE | K_EDIT | K_AUTODEL | K_TRIM))
+			return false;
+	}
+
+	memset(&msg, 0, sizeof(msg));
+	msg.hdr.auxattr = orgmsg->hdr.auxattr & (MSG_HFIELDS_UTF8 | MSG_MIMEATTACH);
+	msg.hdr.when_imported.time = time32(NULL);
+	msg.hdr.when_imported.zone = sys_timezone(&cfg);
+	msg.hdr.when_written = msg.hdr.when_imported;
 
-	now32=time32(NULL);
-	smb_hfield(msg,FORWARDED,sizeof(now32),&now32);
+	smb_hfield_str(&msg, SUBJECT, subject);
+	add_msg_ids(&cfg, &smb, &msg, orgmsg);
 
-	smb_hfield_str(msg,SENDER,useron.alias);
+	smb_hfield_str(&msg,SENDER,useron.alias);
 	SAFEPRINTF(str,"%u",useron.number);
-	smb_hfield_str(msg,SENDEREXT,str);
+	smb_hfield_str(&msg,SENDEREXT,str);
 
 	/* Security logging */
-	msg_client_hfields(msg,&client);
-	smb_hfield_str(msg,SENDERSERVER, server_host_name());
+	msg_client_hfields(&msg,&client);
+	smb_hfield_str(&msg,SENDERSERVER, server_host_name());
 
 	if(usernumber > 0) {
 		username(&cfg,usernumber,touser);
-		smb_hfield_str(msg,RECIPIENT,touser);
+		smb_hfield_str(&msg, RECIPIENT,touser);
 		SAFEPRINTF(str,"%u",usernumber);
-		smb_hfield_str(msg,RECIPIENTEXT,str);
-		msg->idx.to=usernumber;
+		smb_hfield_str(&msg, RECIPIENTEXT,str);
 	} else {
 		SAFECOPY(touser, to);
-		char* addr = touser;
-		char* p = strchr(addr, '@');
-		if(net_type != NET_INTERNET && p != NULL)
-			addr = p + 1;
-		smb_hfield_netaddr(msg, RECIPIENTNETADDR, addr, NULL);
-		if(p != NULL)
+		char* p;
+		if((p = strchr(touser, '@')) != NULL)
 			*p = '\0';
-		smb_hfield_str(msg, RECIPIENT, touser);
+		smb_hfield_str(&msg, RECIPIENT, touser);
 		SAFECOPY(touser, to);
-		msg->idx.to=0;
-	}
-
-	if((i=smb_open_da(&smb))!=SMB_SUCCESS) {
-		errormsg(WHERE,ERR_OPEN,smb.file,i,smb.last_error);
+		const char* addr = touser;
+		if(net_type != NET_INTERNET && p != NULL)
+			addr = p + 1;
+		char fulladdr[128];
+		if(net_type == NET_QWK) {
+			usernumber = qwk_route(&cfg, addr, fulladdr, sizeof(fulladdr) - 1);
+			if(*fulladdr == '\0') {
+				bprintf(text[InvalidNetMailAddr], addr);
+				smb_freemsgmem(&msg);
+				return false; 
+			}
+			addr = fulladdr;
+			SAFEPRINTF(str, "%u", usernumber);
+			smb_hfield_str(&msg, RECIPIENTEXT, str);
+			usernumber = 0;
+		}
+		smb_hfield_bin(&msg, RECIPIENTNETTYPE, net_type);
+		smb_hfield_netaddr(&msg, RECIPIENTNETADDR, addr, &net_type);
+	}
+	if(orgmsg->mime_version != NULL) {
+		safe_snprintf(str, sizeof(str), "MIME-Version: %s", orgmsg->mime_version);
+		smb_hfield_str(&msg, RFC822HEADER, str);
+	}
+	if(orgmsg->content_type != NULL) {
+		safe_snprintf(str, sizeof(str), "Content-type: %s", orgmsg->content_type);
+		smb_hfield_str(&msg, RFC822HEADER, str);
+	}
+	// This header field not strictly required any more:
+	time32_t now32 = time32(NULL);
+	smb_hfield(&msg, FORWARDED, sizeof(now32), &now32);
+
+	const char* br = NULL;
+	const char* pg = nulstr;
+	const char* lt = "<";
+	const char* gt = ">";
+	if(orgmsg->text_subtype != NULL && stricmp(orgmsg->text_subtype, "html") == 0) {
+		lt = "&lt;";
+		gt = "&gt;";
+		br = "<br>";
+		pg = "<p>";
+	}
+
+	if(comment == NULL) {
+		while(online && !msgabort()) {
+			bputs(text[UeditComment]);
+			if(!getstr(str, 70, K_WRAP))
+				break;
+			smb_hfield_string(&msg, SMB_COMMENT, str);
+			smb_hfield_string(&msg, SMB_COMMENT, br);
+		}
+		if(!online || msgabort()) {
+			smb_freemsgmem(&msg);
+			return false; 
+		}
+	} else {
+		if(*comment)
+			smb_hfield_string(&msg, SMB_COMMENT, comment);
+	}
+	if(smb_get_hfield(&msg, SMB_COMMENT, NULL) != NULL)
+		smb_hfield_string(&msg, SMB_COMMENT, pg);
+	smb_hfield_string(&msg, SMB_COMMENT, "-----Forwarded Message-----");
+	smb_hfield_string(&msg, SMB_COMMENT, br);
+	if(orgmsg->from_net.addr != NULL)
+		safe_snprintf(str, sizeof(str), "From: %s %s%s%s"
+			,orgmsg->from, lt, smb_netaddrstr(&orgmsg->from_net, tmp), gt);
+	else
+		safe_snprintf(str, sizeof(str), "From: %s", orgmsg->from);
+	smb_hfield_string(&msg, SMB_COMMENT, str);
+	smb_hfield_string(&msg, SMB_COMMENT, br);
+	safe_snprintf(str, sizeof(str), "Date: %s", msgdate(orgmsg->hdr.when_written, tmp));
+	smb_hfield_string(&msg, SMB_COMMENT, str);
+	smb_hfield_string(&msg, SMB_COMMENT, br);
+	if(orgmsg->to_net.addr != NULL)
+		safe_snprintf(str, sizeof(str), "To: %s %s%s%s"
+			,orgmsg->to, lt, smb_netaddrstr(&orgmsg->to_net, tmp), gt);
+	else
+		safe_snprintf(str, sizeof(str), "To: %s", orgmsg->to);
+	smb_hfield_string(&msg, SMB_COMMENT, str);
+	smb_hfield_string(&msg, SMB_COMMENT, br);
+	safe_snprintf(str, sizeof(str), "Subject: %s", orgmsg->subj);
+	smb_hfield_string(&msg, SMB_COMMENT, str);
+	smb_hfield_string(&msg, SMB_COMMENT, pg);
+
+	// Re-use the original message's data
+	if((result = smb_open_da(&smb)) != SMB_SUCCESS) {
+		smb_freemsgmem(&msg);
+		errormsg(WHERE, ERR_OPEN, smb.file, result, smb.last_error);
 		return false;
 	}
-	if((i=smb_incmsg_dfields(&smb,msg,1))!=SMB_SUCCESS) {
-		errormsg(WHERE,ERR_WRITE,smb.file,i);
+	if((result = smb_incmsg_dfields(&smb, orgmsg, 1)) != SMB_SUCCESS) {
+		smb_freemsgmem(&msg);
+		errormsg(WHERE, ERR_WRITE, smb.file, result, smb.last_error);
 		return false;
 	}
 	smb_close_da(&smb);
 
-	if((i=smb_addmsghdr(&smb,msg,smb_storage_mode(&cfg, &smb)))!=SMB_SUCCESS) {
-		errormsg(WHERE,ERR_WRITE,smb.file,i,smb.last_error);
-		smb_freemsg_dfields(&smb,msg,1);
-		return false;
+	msg.dfield = orgmsg->dfield;
+	msg.hdr.offset = orgmsg->hdr.offset;
+	msg.hdr.total_dfields = orgmsg->hdr.total_dfields;
+
+	if(orgmsg->hdr.auxattr&MSG_FILEATTACH) {
+		copyfattach(usernumber, useron.number, orgmsg->subj);
+		msg.hdr.auxattr |= MSG_FILEATTACH;
 	}
 
-	if(msg->hdr.auxattr&MSG_FILEATTACH)
-		copyfattach(usernumber,useron.number,msg->subj);
+	result = smb_addmsghdr(&smb, &msg, smb_storage_mode(&cfg, &smb));
+	msg.dfield = NULL;
+	smb_freemsgmem(&msg);
+	if(result != SMB_SUCCESS) {
+		errormsg(WHERE, ERR_WRITE, smb.file, result, smb.last_error);
+		smb_freemsg_dfields(&smb, orgmsg, 1);
+		return false;
+	}
 
 	bprintf(text[Forwarded], touser, usernumber);
 	SAFEPRINTF(str, "forwarded mail to %s", touser);
 	logline("E+",str);
-	msg->idx=idx;
-	msg->hdr=hdr;
 
 	if(usernumber==1) {
 		useron.fbacks++;
@@ -1440,6 +1539,7 @@ bool sbbs_t::forwardmail(smbmsg_t* msg, const char* to)
 	putuserrec(&cfg,useron.number,U_ETODAY,5,ultoa(useron.etoday,tmp,10));
 
 	if(usernumber > 0) {
+		int i;
 		for(i=1;i<=cfg.sys_nodes;i++) { /* Tell user, if online */
 			getnodedat(i,&node,0);
 			if(node.useron==usernumber && !(node.misc&NODE_POFF)
diff --git a/src/sbbs3/xtrn_sec.cpp b/src/sbbs3/xtrn_sec.cpp
index 33cc509d4d54b3b5fb4738cfd54fb188850c44c8..9c71e517439c19ce743cfec0eaa787f561cc1076 100644
--- a/src/sbbs3/xtrn_sec.cpp
+++ b/src/sbbs3/xtrn_sec.cpp
@@ -151,6 +151,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 	struct tm tm;
 	struct tm tl;
 	stats_t stats;
+	long term = term_supports();
 
 	char	node_dir[MAX_PATH+1];
 	char	ctrl_dir[MAX_PATH+1];
@@ -212,8 +213,8 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 			,cfg.sys_nodes						/* Total system nodes */
 			,cfg.node_num						/* Current node */
 			,tleft								/* User Timeleft in seconds */
-			,term_supports(ANSI)				/* User ANSI ? (Yes/Mono/No) */
-				? term_supports(COLOR)
+			,(term & ANSI)						/* User ANSI ? (Yes/Mono/No) */
+				? (term & COLOR)
 				? "Yes":"Mono":"No"
 			,rows								/* User Screen lines */
 			,useron.cdt+useron.freecdt);		/* User Credits */
@@ -326,7 +327,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 		lfexpand(str,misc);
 		write(file,str,strlen(str));
 
-		safe_snprintf(str, sizeof(str), "%lu\n%s\n%lu\n%ld\n%u\n%u\n%u\n%ld\n%u\n"
+		safe_snprintf(str, sizeof(str), "%lu\n%s\n%lu\n%ld\n%u\n%u\n%u\n%d\n%u\n"
 			,useron.cdt+useron.freecdt			/* Gold */
 			,unixtodstr(&cfg,useron.laston,tmp)	/* User last on date */
 			,cols 								/* User screen width */
@@ -334,7 +335,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 			,useron.level						/* User SL */
 			,0									/* Cosysop? */
 			,SYSOP								/* Sysop? (1/0) */
-			,term_supports(ANSI)				/* ANSI ? (1/0) */
+			,INT_TO_BOOL(term & ANSI)			/* ANSI ? (1/0) */
 			,online==ON_REMOTE);				/* Remote (1/0) */
 		lfexpand(str,misc);
 		write(file,str,strlen(str));
@@ -416,8 +417,8 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 			,unixtodstr(&cfg,useron.laston,tmp)	/* 17: User last on date */
 			,tleft								/* 18: User time left in sec */
 			,tleft/60							/* 19: User time left in min */
-			,useron.misc&NO_EXASCII 			/* 20: GR if COLOR ANSI */
-				? "7E" : (useron.misc&(ANSI|COLOR))==(ANSI|COLOR) ? "GR" : "NG");
+			,(term & NO_EXASCII)				/* 20: GR if COLOR ANSI */
+				? "7E" : (term & (ANSI|COLOR)) == (ANSI|COLOR) ? "GR" : "NG");
 		lfexpand(str,misc);
 		write(file,str,strlen(str));
 
@@ -451,7 +452,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 
 		localtime_r(&ns_time,&tm);
 		safe_snprintf(str, sizeof(str), "%c\n%c\n%u\n%lu\n%02d/%02d/%02d\n"
-			,(useron.misc&(NO_EXASCII|ANSI|COLOR))==ANSI
+			,(term & (NO_EXASCII|ANSI|COLOR)) == ANSI
 				? 'Y':'N'                       /* 39: ANSI supported but NG mode */
 			,'Y'                                /* 40: Use record locking */
 			,cfg.color[clr_external]			/* 41: BBS default color */
@@ -522,11 +523,11 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 			*(p++)=0;
 		else
 			p=nulstr;
-		safe_snprintf(str, sizeof(str), "%s\n%s\n%s\n%ld\n%u\n%lu\n"
+		safe_snprintf(str, sizeof(str), "%s\n%s\n%s\n%d\n%u\n%lu\n"
 			,tmp								/* User's firstname */
 			,p									/* User's lastname */
 			,useron.location					/* User's city */
-			,term_supports(ANSI)				/* 1=ANSI 0=ASCII */
+			,INT_TO_BOOL(term & ANSI)			/* 1=ANSI 0=ASCII */
 			,useron.level						/* Security level */
 			,tleft/60); 						/* Time left in minutes */
 		strupr(str);
@@ -577,7 +578,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 		if(useron.misc&DELETED) c|=(1<<0);
 		if(useron.misc&CLRSCRN) c|=(1<<1);
 		if(useron.misc&UPAUSE)	 c|=(1<<2);
-		if(term_supports(ANSI))	c|=(1<<3);
+		if(term & ANSI)			c|=(1<<3);
 		if(useron.sex=='F')     c|=(1<<7);
 		write(file,&c,1);						/* Attrib */
 		write(file,&useron.flags1,4);			/* Flags */
@@ -655,7 +656,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 		write(file,&c,1);						/* ScreenClear */
 		c=useron.misc&UPAUSE ? 1:0;
 		write(file,&c,1);						/* MorePrompts */
-		c=useron.misc&NO_EXASCII ? 0:1;
+		c=(term & NO_EXASCII) ? 0:1;
 		write(file,&c,1);						/* GraphicsMode */
 		c=useron.xedit ? 1:0;
 		write(file,&c,1);						/* ExternEdit */
@@ -666,7 +667,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 		write(file,str,49); 					/* ChatReason */
 		c=0;
 		write(file,&c,1);						/* ExternLogoff */
-		c=(char)term_supports(ANSI);
+		c=(char)INT_TO_BOOL(term & ANSI);
 		write(file,&c,1);						/* ANSI_Capable */
 		close(file);
 	}
@@ -713,7 +714,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 			,useron.location					/* User location */
 			,useron.level						/* Security level */
 			,tleft/60							/* Time left in min */
-			,term_supports(ANSI) ? "COLOR":"MONO"  /* ANSI ??? */
+			,(term & ANSI) ? "COLOR":"MONO"		/* ANSI ??? */
 			,useron.pass						/* Password */
 			,useron.number);					/* User number */
 		lfexpand(str,misc);
@@ -801,8 +802,8 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 			,startup->answer_sound[0] ? -1:0	/* Caller Alarm on/off */
 			,' ' 								/* Sysop next flag */
 			,0									/* Error corrected */
-			,useron.misc&NO_EXASCII ? '7'       /* Graphics mode */
-				: (useron.misc&(COLOR|ANSI))==(COLOR|ANSI) ? 'Y':'N'
+			,(term & NO_EXASCII) ? '7'			/* Graphics mode */
+				: (term & (COLOR|ANSI)) == (COLOR|ANSI) ? 'Y':'N'
 			,'A'                                /* Node chat status */
 			,(uint)dte_rate 					/* DTE Port Speed */
 			,connection 						/* Connection description */
@@ -869,11 +870,11 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 		l=0L;
 		write(file,&l,4);						/* Memorized message number */
 
-		safe_snprintf(str, sizeof(str), "%d%c%c%ld%s%c%c%d%d%d%c%c"
+		safe_snprintf(str, sizeof(str), "%d%c%c%d%s%c%c%d%d%d%c%c"
 			,cfg.com_port						/* COM Port number */
 			,' ' 								/* Reserved */
 			,' ' 								/* "" */
-			,term_supports(ANSI)				/* 1=ANSI 0=NO ANSI */
+			,INT_TO_BOOL(term & ANSI)			/* 1=ANSI 0=NO ANSI */
 			,"01-01-80"                         /* last event date */
 			,0,0								/* last event minute */
 			,0									/* caller exited to dos */
@@ -1036,7 +1037,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 			"%s\n%s\n%lu\n%s\n%u\n%u\n%u\n%u\n%u\n%lu\n%u\n"
 			"%lu\n%lu\n%s\n%s\n"
 			,dropdir
-			,term_supports(ANSI) ? "TRUE":"FALSE"  /* ANSI ? True or False */
+			,(term & ANSI) ? "TRUE":"FALSE"		/* ANSI ? True or False */
 			,useron.level						/* Security level */
 			,useron.uls 						/* Total uploads */
 			,useron.dls 						/* Total downloads */
@@ -1102,10 +1103,10 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 			return; 
 		}
 
-		safe_snprintf(str, sizeof(str), "%s\n%ld\n%d\n%lu\n%lu\n%u\n%lu\n"
+		safe_snprintf(str, sizeof(str), "%s\n%d\n%d\n%lu\n%lu\n%u\n%lu\n"
 			,name								/* Complete name of user */
-			,term_supports(ANSI)	 			/* ANSI ? */
-			,term_supports(NO_EXASCII) ? 0:1	/* IBM characters ? */
+			,INT_TO_BOOL(term & ANSI)			/* ANSI ? */
+			,!INT_TO_BOOL(term & NO_EXASCII)	/* IBM characters ? */
 			,rows								/* Page length */
 			,dte_rate							/* Baud rate */
 			,online==ON_LOCAL ? 0:cfg.com_port	/* COM port */
@@ -1133,7 +1134,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 			,useron.pass						/* User's password */
 			,useron.level						/* User's level */
 			,useron.misc&EXPERT ? 'Y':'N'       /* Expert? */
-			,term_supports(ANSI) ? 'Y':'N'      /* ANSI? */
+			,(term & ANSI) ? 'Y':'N'			/* ANSI? */
 			,tleft/60							/* Minutes left */
 			,useron.phone						/* User's phone number */
 			,useron.location					/* User's city and state */
@@ -1170,7 +1171,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 		}
 
 		safe_snprintf(str, sizeof(str), "%d\n%d\n%lu\n%s%c\n%d\n%s\n%s\n%d\n%ld\n"
-			"%ld\n%d\n"
+			"%d\n%d\n"
 			,misc&(XTRN_STDIO|XTRN_CONIO) ? 0 /* Local */ : 2 /* Telnet */
 			,misc&(XTRN_STDIO|XTRN_CONIO) ? INVALID_SOCKET : client_socket_dup
 			,dte_rate
@@ -1180,7 +1181,7 @@ void sbbs_t::xtrndat(const char *name, const char *dropdir, uchar type, ulong tl
 			,name
 			,useron.level
 			,tleft/60
-			,term_supports(ANSI)
+			,INT_TO_BOOL(term & ANSI)
 			,cfg.node_num);
 		lfexpand(str,misc);
 		write(file,str,strlen(str));
diff --git a/src/smblib/smblib.c b/src/smblib/smblib.c
index 4c25378f59e9febe80d089e274ffa19ace739022..9f77bbeb88ad2ad8064f2713c76e0545a978001b 100644
--- a/src/smblib/smblib.c
+++ b/src/smblib/smblib.c
@@ -713,34 +713,25 @@ static void set_convenience_ptr(smbmsg_t* msg, uint16_t hfield_type, void* hfiel
 {
 	switch(hfield_type) {	/* convenience variables */
 		case SENDER:
-			if(msg->from==NULL || *(msg->from)==0) {
-				msg->from=(char*)hfield_dat;
-				break; 
-			}
-		case FORWARDED: 	/* fall through */
+			msg->from=(char*)hfield_dat;
+			break; 
+		case FORWARDED:
 			msg->forwarded = TRUE;
-			msg->to_ext = NULL;
-			memset(&msg->to_net, 0, sizeof(msg->to_net));
 			break;
 		case SENDERAGENT:
-			if(!msg->forwarded)
-				msg->from_agent=*(uint16_t *)hfield_dat;
+			msg->from_agent=*(uint16_t *)hfield_dat;
 			break;
 		case SENDEREXT:
-			if(!msg->forwarded)
-				msg->from_ext=(char*)hfield_dat;
+			msg->from_ext=(char*)hfield_dat;
 			break;
 		case SENDERORG:
-			if(!msg->forwarded)
-				msg->from_org=(char*)hfield_dat;
+			msg->from_org=(char*)hfield_dat;
 			break;
 		case SENDERNETTYPE:
-			if(!msg->forwarded)
-				msg->from_net.type=*(uint16_t *)hfield_dat;
+			msg->from_net.type=*(uint16_t *)hfield_dat;
 			break;
 		case SENDERNETADDR:
-			if(!msg->forwarded)
-				msg->from_net.addr=(char*)hfield_dat;
+			msg->from_net.addr=(char*)hfield_dat;
 			break;
 		case SENDERIPADDR:
 			msg->from_ip=(char*)hfield_dat;
@@ -1227,13 +1218,23 @@ int	SMBCALL smb_hfield_add_list(smbmsg_t* msg, hfield_t** hfield_list, void** hf
 }
 
 /****************************************************************************/
-/* Convenience function to add an ASCIIZ string header field				*/
+/* Convenience function to add an ASCIIZ string header field (or blank)		*/
 /****************************************************************************/
 int SMBCALL smb_hfield_add_str(smbmsg_t* msg, uint16_t type, const char* str, BOOL insert)
 {
 	return smb_hfield_add(msg, type, str==NULL ? 0:strlen(str), (void*)str, insert);
 }
 
+/****************************************************************************/
+/* Convenience function to add an ASCIIZ string header field (NULL ignored)	*/
+/****************************************************************************/
+int SMBCALL smb_hfield_string(smbmsg_t* msg, uint16_t type, const char* str)
+{
+	if(str == NULL)
+		return SMB_ERR_HDR_FIELD;
+	return smb_hfield_add(msg, type, strlen(str), (void*)str, /* insert */FALSE);
+}
+
 /****************************************************************************/
 /* Convenience function to a network address header field to msg			*/
 /* Pass NULL for net_type to have the auto-detected net_type hfield	added	*/
diff --git a/src/smblib/smblib.h b/src/smblib/smblib.h
index 5c1c55e7a9340843ca0d8c69690c6ec1cd6d6309..fbe37ffbf99c2fc1101c72c210673328e7ba8063 100644
--- a/src/smblib/smblib.h
+++ b/src/smblib/smblib.h
@@ -160,6 +160,7 @@ SMBEXPORT int		SMBCALL smb_hfield_append(smbmsg_t* msg, uint16_t type, size_t le
 SMBEXPORT int		SMBCALL smb_hfield_append_str(smbmsg_t* msg, uint16_t type, const char* data);
 SMBEXPORT int		SMBCALL smb_hfield_add_list(smbmsg_t* msg, hfield_t** hfield_list, void** hfield_dat, BOOL insert);
 SMBEXPORT int		SMBCALL smb_hfield_add_netaddr(smbmsg_t* msg, uint16_t type, const char* str, uint16_t* nettype, BOOL insert);
+SMBEXPORT int		SMBCALL	smb_hfield_string(smbmsg_t*, uint16_t type, const char*);
 /* Convenience macro: */
 #define smb_hfield_bin(msg, type, data) smb_hfield_add(msg, type, sizeof(data), &(data), /* insert: */FALSE)
 /* Backward compatibility macros: */
diff --git a/src/smblib/smbtxt.c b/src/smblib/smbtxt.c
index 60794b91977ab98dd2dc710f7efc52d48e07b47e..0ffce0d1d5320d54dda792feee3625b5782544da 100644
--- a/src/smblib/smbtxt.c
+++ b/src/smblib/smbtxt.c
@@ -45,6 +45,7 @@
 char* SMBCALL smb_getmsgtxt(smb_t* smb, smbmsg_t* msg, ulong mode)
 {
 	char*	buf;
+	char*	preamble;
 	char*	lzhbuf;
 	char*	p;
 	char*	str;
@@ -107,6 +108,7 @@ char* SMBCALL smb_getmsgtxt(smb_t* smb, smbmsg_t* msg, ulong mode)
 			buf[l] = 0;
 		}
 	}
+	preamble = strdup(buf);
 
 	for(i=0;i<(uint)msg->hdr.total_dfields;i++) {
 		if(msg->dfield[i].length<=sizeof(xlat))
@@ -146,6 +148,7 @@ char* SMBCALL smb_getmsgtxt(smb_t* smb, smbmsg_t* msg, ulong mode)
 					,"%s malloc failure of %ld bytes for LZH buffer"
 					, __FUNCTION__, length);
 				free(buf);
+				free(preamble);
 				return(NULL);
 			}
 			if(smb_fread(smb,lzhbuf,length,smb->sdt_fp) != length) {
@@ -154,6 +157,7 @@ char* SMBCALL smb_getmsgtxt(smb_t* smb, smbmsg_t* msg, ulong mode)
 					, __FUNCTION__, length);
 				free(lzhbuf);
 				free(buf);
+				free(preamble);
 				return(NULL);
 			}
 			lzhlen=*(int32_t*)lzhbuf;
@@ -163,6 +167,7 @@ char* SMBCALL smb_getmsgtxt(smb_t* smb, smbmsg_t* msg, ulong mode)
 					, __FUNCTION__, l+lzhlen+3L);
 				free(lzhbuf);
 				free(buf);
+				free(preamble);
 				return(NULL);
 			}
 			buf=p;
@@ -176,6 +181,7 @@ char* SMBCALL smb_getmsgtxt(smb_t* smb, smbmsg_t* msg, ulong mode)
 					,"%s realloc failure of %ld bytes for text buffer"
 					, __FUNCTION__, l+length+3L);
 				free(buf);
+				free(preamble);
 				return(NULL);
 			}
 			buf=p;
@@ -196,9 +202,18 @@ char* SMBCALL smb_getmsgtxt(smb_t* smb, smbmsg_t* msg, ulong mode)
 
 	if(mode&GETMSGTXT_PLAIN) {
 		char* plaintext = smb_getplaintext(msg, buf);
-		if(plaintext != NULL)
-			return plaintext;
+		if(plaintext != NULL) {
+			buf = malloc(strlen(preamble) + strlen(plaintext) + 1);
+			if(buf == NULL)
+				buf = plaintext;
+			else {
+				strcpy(buf, preamble);
+				strcat(buf, plaintext);
+				free(plaintext);
+			}
+		}
 	}
+	free(preamble);
 	return(buf);
 }
 
@@ -480,7 +495,6 @@ char* SMBCALL smb_getplaintext(smbmsg_t* msg, char* buf)
 	const char*	txt;
 	enum content_transfer_encoding xfer_encoding = CONTENT_TRANFER_ENCODING_NONE;
 
-	FREE_AND_NULL(msg->text_subtype);
 	if(msg->mime_version == NULL || msg->content_type == NULL)	/* not MIME */
 		return NULL;
 	txt = mime_getcontent(buf, msg->content_type, "text/plain", 0, &xfer_encoding, &msg->text_charset
@@ -490,9 +504,12 @@ char* SMBCALL smb_getplaintext(smbmsg_t* msg, char* buf)
 			,/* attachment: */NULL, /* attachment_len: */0, /* index: */0);
 		if(txt == NULL)
 			return NULL;
+		free(msg->text_subtype);
 		msg->text_subtype = strdup("html");
-	} else
+	} else {
+		free(msg->text_subtype);
 		msg->text_subtype = strdup("plain");
+	}
 
 	memmove(buf, txt, strlen(txt)+1);
 	if(*buf == 0)	/* No decoding necessary */
diff --git a/webv4/lib/events/mail.js b/webv4/lib/events/mail.js
index 986b81875f53cb41f7035cd4ecaef5a9f0c21c33..4aca79415a478d83f9ad141f1cb6fbc471fcedfb 100644
--- a/webv4/lib/events/mail.js
+++ b/webv4/lib/events/mail.js
@@ -6,7 +6,7 @@ function cycle() {
     if (user.number < 1 || user.alias == settings.guest) return;
     if (time() - last_run <= frequency) return;
     last_run = time();
-    const count = user.stats.mail_waiting;
+    const count = user.stats.unread_mail_waiting;
     if (count > 0 || (count == 0 && last_count > 0)) {
         emit({ event: 'mail', data: JSON.stringify({ count: count })});
     }
diff --git a/webv4/lib/locale/en_us.ini b/webv4/lib/locale/en_us.ini
index c7391e85be75cd9fd9ea9fd21aabd2a8140ff285..655ad46eeb74c0d3a0420ad78ba2c0d42028886d 100644
--- a/webv4/lib/locale/en_us.ini
+++ b/webv4/lib/locale/en_us.ini
@@ -24,6 +24,7 @@ label_message_date = on
 label_message_subject = Subject
 label_tab_inbox = Inbox
 label_tab_sent = Sent
+label_new_message = New
 
 [page_register]
 title = Register
diff --git a/webv4/pages/000-mail.xjs b/webv4/pages/000-mail.xjs
index 5aeaea94b2dcbaef5460222286a277e1239e8ac3..c7e662e58c3e922af0d08ff5b53f5662ec69ff94 100644
--- a/webv4/pages/000-mail.xjs
+++ b/webv4/pages/000-mail.xjs
@@ -18,7 +18,7 @@
 				<div class="checkbox">
 					<label class="checkbox-inline">
 						<input id="check-<? write(header.number); ?>" type="checkbox" class="mail-select">
-<? write(header.attr&MSG_READ ? '' : '<span class="badge new">New</span>') ?>
+                        <? write(header.attr&MSG_READ ? '' : '<span class="badge new">' + locale.strings.page_mail.label_new_message + '</span>') ?>
 					</label>
 				</div>
 			</div>