diff --git a/.gitlab-ci-unix.yml b/.gitlab-ci-unix.yml
index dfea88c0011fae4f44d0c1721e93f1c55b1136cf..12787421ec66babc23a2cd52fcdc4c730f82b234 100644
--- a/.gitlab-ci-unix.yml
+++ b/.gitlab-ci-unix.yml
@@ -46,7 +46,7 @@ spec:
   script:
     - cd 3rdp/build
     - touch depend
-    - JS_CONFIGURE_ARGS="--cache-file=../../../../../build/smconfig.cache" $[[ inputs.gnu_make ]] $[[ inputs.build_flags ]] $BUILD_ARGS libmozjs
+    - $[[ inputs.gnu_make ]] $[[ inputs.build_flags ]] $BUILD_ARGS libmozjs
     - mkdir -p "/tmp/gitlab-runner/$[[ inputs.os ]]-$[[ inputs.platform ]]-${CI_PIPELINE_ID}"
     - cd ../..
     - tar -czf "/tmp/gitlab-runner/$[[ inputs.os ]]-$[[ inputs.platform ]]-${CI_PIPELINE_ID}/spidermonkey.tgz" 3rdp/*.release/mozjs
@@ -56,10 +56,6 @@ spec:
     - if: '"$[[ inputs.no_javascript ]]" == "yes"'
       when: never
     - !reference [.rules, rules]
-  cache:
-    key: $CI_JOB_NAME
-    paths:
-      - 3rdp/build/smconfig.cache*
 
 "$[[ inputs.os ]]-$[[ inputs.platform ]] [cryptlib]":
   extends:
diff --git a/exec/load/dd_lightbar_menu.js b/exec/load/dd_lightbar_menu.js
index 9a9b04a6c0a5b00e6b6c4074d8e38e9e9564a665..dfa31d3c91ea961756c29663ae749d87d1a14041 100644
--- a/exec/load/dd_lightbar_menu.js
+++ b/exec/load/dd_lightbar_menu.js
@@ -355,6 +355,19 @@ By default, DDLightbarMenu ignores the isSelectable attribute of items and consi
 selectable (for efficiency).  To enable usage of unselectable items, set the allowUnselectableItems
 property to true:
 lbMenu.allowUnselectableItems = true;
+
+
+If the user's terminal doesn't support ANSI, DDLightbarMenu will work in a non-lightbar
+mode. When not using a lightbar interface, DDLightbarMenu will automatically use numbered
+mode, where the menu will output numbers to the left of the menu items and let the user
+type a number to choose an item.
+You can also tell DDLightbarMenu to not work in lightbar mode if you want a more traditional
+user interface (colors will still be supported) by setting the allowANSI property to false:
+lbMenu.allowANSI = false;
+
+For the traditional/non-lightbar mode, you can customize the prompt text that is used, by
+changing the nonANSIPromptText property. For instance:
+lbMenu.nonANSIPromptText = "Type a number to choose an item: ";
 */
 
 "use strict";
@@ -535,6 +548,9 @@ function DDLightbarMenu(pX, pY, pWidth, pHeight)
 	// Whether or not to allow ANSI behavior. Mainly for testing (this should be true).
 	this.allowANSI = true;
 
+	// Text to use for the user input prompt for the non-ANSI interface
+	this.nonANSIPromptText = "\x01n\x01c\x01hY\x01n\x01cour \x01hC\x01n\x01choice\x01h\x01g: \x01c";
+
 	// Member functions
 	this.Add = DDLightbarMenu_Add;
 	this.Remove = DDLightbarMenu_Remove;
@@ -2291,27 +2307,62 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes)
 	{
 		// The user's terminal doesn't support ANSI
 		var userAnswerIsValid = false;
+		var writePromptText = true;
 		do
 		{
-			console.print("\x01n\x01c\x01hY\x01n\x01cour \x01hC\x01n\x01choice\x01h\x01g: \x01c");
+			if (writePromptText)
+			{
+				if (typeof(this.nonANSIPromptText) === "string" && console.strlen(this.nonANSIPromptText) > 0)
+					console.print(this.nonANSIPromptText);
+				else
+					console.print("\x01n\x01c\x01hY\x01n\x01cour \x01hC\x01n\x01choice\x01h\x01g: \x01c");
+			}
+			writePromptText = true; // Default value
 			console.attributes = "N";
-			var userEnteredItemNum = console.getnum(numItems);
-			this.lastUserInput = userEnteredItemNum.toString();
-			if (!console.aborted && userEnteredItemNum > 0)
+			var inputMode = K_NOECHO|K_NOSPIN|K_NOCRLF;
+			var userInput = console.getkey(inputMode);
+			var userInputUpper = userInput.toUpperCase();
+			// Set this.lastUserInput if it's valid
+			if (console.aborted || userInputUpper == "Q" || userInput == CTRL_C || userInput == KEY_ESC)
+			{
+				if (userInputUpper == "Q")
+					this.lastUserInput = "Q";
+				else if (userInput == CTRL_C || userInput == KEY_ESC)
+					this.lastUserInput = userInput;
+				else if (console.aborted)
+					this.lastUserInput = CTRL_C;
+				userAnswerIsValid = true;
+			}
+			else if (this.QuitKeysIncludes(userInput))
 			{
-				if (this.ItemIsSelectable(userEnteredItemNum-1))
+				this.lastUserInput = userInput;
+				userAnswerIsValid = true;
+			}
+			else if (/[0-9]/.test(userInput))
+			{
+				// Put the user's input back in the input buffer to
+				// be used for getting the rest of the message number.
+				console.ungetstr(userInput);
+				var userEnteredItemNum = console.getnum(numItems);
+				this.lastUserInput = userEnteredItemNum.toString();
+				if (!console.aborted && userEnteredItemNum > 0)
+				{
+					if (this.ItemIsSelectable(userEnteredItemNum-1))
+					{
+						var chosenItem = this.GetItem(userEnteredItemNum-1);
+						if (typeof(chosenItem) === "object" && chosenItem.hasOwnProperty("retval"))
+							retVal = chosenItem.retval;
+						userAnswerIsValid = true;
+					}
+				}
+				else
 				{
-					var chosenItem = this.GetItem(userEnteredItemNum-1);
-					if (typeof(chosenItem) === "object" && chosenItem.hasOwnProperty("retval"))
-						retVal = chosenItem.retval;
+					this.lastUserInput = "Q"; // To signify quitting
 					userAnswerIsValid = true;
 				}
 			}
 			else
-			{
-				this.lastUserInput = "Q"; // To signify quitting
-				userAnswerIsValid = true;
-			}
+				writePromptText = false; // Invalid user input
 		} while (!userAnswerIsValid && bbs.online && !js.terminated);
 	}
 
diff --git a/exec/load/portdefs.js b/exec/load/portdefs.js
index 1ce9cadfd0707f9824dd3377b44a9d17a5e6391f..37f0abaca847ab6b7d080c35b76ff09edb90bfd4 100644
--- a/exec/load/portdefs.js
+++ b/exec/load/portdefs.js
@@ -7,7 +7,7 @@
 // Mainly used for outgoing connections to URIs without a specified port
 
 // Duplicates port numbers for service name aliases are included
-// (e.g. both "nttp" and "news")
+// (e.g. both "nntp" and "news")
 
 var standard_service_port = {
 	"systat":		11,		// Active Users
diff --git a/exec/tests/crypt/cryptkeyset.js b/exec/tests/crypt/cryptkeyset.js
new file mode 100644
index 0000000000000000000000000000000000000000..2eeb6104929e130bc47557edce9e99569d4f6351
--- /dev/null
+++ b/exec/tests/crypt/cryptkeyset.js
@@ -0,0 +1,3 @@
+var ks = CryptKeyset("tmpkeyset", CryptKeyset.KEYOPT.CREATE);
+ks.close();
+file_remove("tmpkeyset");
diff --git a/src/conio/cterm.c b/src/conio/cterm.c
index cbf872daa9e65b04137a88cb21eedcb5db4c75f5..9875940932ea53db1147547acf47ee6e485802bb 100644
--- a/src/conio/cterm.c
+++ b/src/conio/cterm.c
@@ -4724,7 +4724,7 @@ cterm_reset(struct cterminal *cterm)
 
 struct cterminal* cterm_init(int height, int width, int xpos, int ypos, int backlines, int backcols, struct vmem_cell *scrollback, int emulation)
 {
-	char	*revision="$Revision: 1.319 $";
+	char	*revision="$Revision: 1.320 $";
 	char *in;
 	char	*out;
 	struct cterminal *cterm;
diff --git a/src/sbbs3/ssl.c b/src/sbbs3/ssl.c
index 0f1645386408f3c9bef3ff1fc37d3899bea74f4f..c7d8075d0fee9060936360642e26e237df29f3d8 100644
--- a/src/sbbs3/ssl.c
+++ b/src/sbbs3/ssl.c
@@ -310,7 +310,6 @@ static void internal_do_cryptInit(void)
 	if ((ret = cryptInit()) == CRYPT_OK) {
 		cryptAddRandom(NULL, CRYPT_RANDOM_SLOWPOLL);
 		atexit(do_cryptEnd);
-		cryptlib_initialized = true;
 		cryptInit_error = CRYPT_OK;
 	}
 	else {
@@ -319,28 +318,24 @@ static void internal_do_cryptInit(void)
 	ret = cryptGetAttribute(CRYPT_UNUSED, CRYPT_OPTION_INFO_MAJORVERSION, &maj);
 	if (cryptStatusError(ret)) {
 		cryptInit_error = ret;
-		cryptlib_initialized = false;
 		cryptEnd();
 		return;
 	}
 	ret = cryptGetAttribute(CRYPT_UNUSED, CRYPT_OPTION_INFO_MINORVERSION, &min);
 	if (cryptStatusError(ret)) {
 		cryptInit_error = ret;
-		cryptlib_initialized = false;
 		cryptEnd();
 		return;
 	}
 	ret = cryptGetAttribute(CRYPT_UNUSED, CRYPT_OPTION_INFO_STEPPING, &stp);
 	if (cryptStatusError(ret)) {
 		cryptInit_error = ret;
-		cryptlib_initialized = false;
 		cryptEnd();
 		return;
 	}
 	tmp = (maj * 100) + (min * 10) + stp;
 	if (tmp != CRYPTLIB_VERSION) {
 		cryptInit_error = CRYPT_ERROR_INVALID;
-		cryptlib_initialized = false;
 		cryptEnd();
 		if (asprintf(&cryptfail, "Incorrect cryptlib version %d (expected %d)", tmp, CRYPTLIB_VERSION) == -1)
 			cryptfail = NULL;
@@ -349,12 +344,12 @@ static void internal_do_cryptInit(void)
 	ret = cryptGetAttributeString(CRYPT_UNUSED, CRYPT_OPTION_INFO_PATCHES, patches, &stp);
 	if (cryptStatusError(ret) || stp != 32 || memcmp(patches, CRYPTLIB_PATCHES, 32) != 0) {
 		cryptInit_error = ret;
-		cryptlib_initialized = false;
 		cryptEnd();
 		if (asprintf(&cryptfail, "Incorrect cryptlib patch set %.32s (expected %s)", patches, CRYPTLIB_PATCHES) == -1)
 			cryptfail = NULL;
 		return;
 	}
+	cryptlib_initialized = true;
 	return;
 }
 
@@ -409,7 +404,7 @@ bool ssl_sync(scfg_t *scfg, int (*lprintf)(int level, const char* fmt, ...))
 				// Paranoia... keep zero as initial value only.
 				if (cert_epoch == 0)
 					cert_epoch = 1;
-				pthread_mutex_lock(&ssl_cert_list_mutex);
+				assert_pthread_mutex_lock(&ssl_cert_list_mutex);
 				while (cert_list) {
 					struct cert_list *old;
 					old = cert_list;
@@ -417,7 +412,7 @@ bool ssl_sync(scfg_t *scfg, int (*lprintf)(int level, const char* fmt, ...))
 					cryptDestroyContext(old->cert);
 					free(old);
 				}
-				pthread_mutex_unlock(&ssl_cert_list_mutex);
+				assert_pthread_mutex_unlock(&ssl_cert_list_mutex);
 				if (!rwlock_unlock(&cert_epoch_lock)) {
 					lprintf(LOG_ERR, "Unable to unlock cert_epoch_lock for write at %d", __LINE__);
 				}
@@ -471,11 +466,11 @@ static struct cert_list * get_ssl_cert(scfg_t *cfg, int (*lprintf)(int level, co
 	}
 	cert_entry->next = NULL;
 
-	pthread_mutex_lock(&get_ssl_cert_mutex);
+	assert_pthread_mutex_lock(&get_ssl_cert_mutex);
 	/* Get the certificate... first try loading it from a file... */
 	if (cryptStatusOK(cryptKeysetOpen(&ssl_keyset, CRYPT_UNUSED, CRYPT_KEYSET_FILE, cert_path, CRYPT_KEYOPT_READONLY))) {
 		if (!DO("getting private key", ssl_keyset, cryptGetPrivateKey(ssl_keyset, &cert_entry->cert, CRYPT_KEYID_NAME, "ssl_cert", cfg->sys_pass))) {
-			pthread_mutex_unlock(&get_ssl_cert_mutex);
+			assert_pthread_mutex_unlock(&get_ssl_cert_mutex);
 			free(cert_entry);
 			return NULL;
 		}
@@ -483,7 +478,7 @@ static struct cert_list * get_ssl_cert(scfg_t *cfg, int (*lprintf)(int level, co
 	else {
 		/* Couldn't do that... create a new context and use the cert from there... */
 		if (!DO("creating TLS context", CRYPT_UNUSED, cryptCreateContext(&cert_entry->cert, CRYPT_UNUSED, CRYPT_ALGO_RSA))) {
-			pthread_mutex_unlock(&get_ssl_cert_mutex);
+			assert_pthread_mutex_unlock(&get_ssl_cert_mutex);
 			free(cert_entry);
 			return NULL;
 		}
@@ -541,7 +536,7 @@ static struct cert_list * get_ssl_cert(scfg_t *cfg, int (*lprintf)(int level, co
 	}
 
 	cryptKeysetClose(ssl_keyset);
-	pthread_mutex_unlock(&get_ssl_cert_mutex);
+	assert_pthread_mutex_unlock(&get_ssl_cert_mutex);
 
 	if (cert_entry->cert == -1) {
 		free(cert_entry);
@@ -559,7 +554,7 @@ failure_return_2:
 	cryptKeysetClose(ssl_keyset);
 failure_return_1:
 	cryptDestroyContext(cert_entry->cert);
-	pthread_mutex_unlock(&get_ssl_cert_mutex);
+	assert_pthread_mutex_unlock(&get_ssl_cert_mutex);
 	cert_path[0] = 0;
 	free(cert_entry);
 	return NULL;
@@ -574,10 +569,10 @@ static struct cert_list *get_sess_list_entry(scfg_t *cfg, int (*lprintf)(int lev
 		lprintf(LOG_ERR, "Failed to lock cert_epoch_lock for read at %d", __LINE__);
 		return NULL;
 	}
-	pthread_mutex_lock(&ssl_cert_list_mutex);
+	assert_pthread_mutex_lock(&ssl_cert_list_mutex);
 	while (1) {
 		if (cert_list == NULL) {
-			pthread_mutex_unlock(&ssl_cert_list_mutex);
+			assert_pthread_mutex_unlock(&ssl_cert_list_mutex);
 			if (!rwlock_rdlock(&cert_epoch_lock)) {
 				lprintf(LOG_ERR, "Failed to unlock cert_epoch_lock for read at %d", __LINE__);
 			}
@@ -590,7 +585,7 @@ static struct cert_list *get_sess_list_entry(scfg_t *cfg, int (*lprintf)(int lev
 		cryptDestroyContext(ret->cert);
 		free(ret);
 	}
-	pthread_mutex_unlock(&ssl_cert_list_mutex);
+	assert_pthread_mutex_unlock(&ssl_cert_list_mutex);
 	if (!rwlock_rdlock(&cert_epoch_lock)) {
 		lprintf(LOG_ERR, "Failed to unlock cert_epoch_lock for read at %d", __LINE__);
 	}
@@ -608,17 +603,17 @@ int add_private_key(scfg_t *cfg, int (*lprintf)(int level, const char* fmt, ...)
 	}
 	ret = cryptSetAttribute(csess, CRYPT_SESSINFO_PRIVATEKEY, sess->cert);
 	if (cryptStatusOK(ret)) {
-		pthread_mutex_lock(&ssl_sess_list_mutex);
+		assert_pthread_mutex_lock(&ssl_sess_list_mutex);
 		sess->next = sess_list;
 		sess_list = sess;
-		pthread_mutex_unlock(&ssl_sess_list_mutex);
+		assert_pthread_mutex_unlock(&ssl_sess_list_mutex);
 		sess->sess = csess;
 	}
 	else {
-		pthread_mutex_lock(&ssl_cert_list_mutex);
+		assert_pthread_mutex_lock(&ssl_cert_list_mutex);
 		sess->next = cert_list;
 		cert_list = sess;
-		pthread_mutex_unlock(&ssl_cert_list_mutex);
+		assert_pthread_mutex_unlock(&ssl_cert_list_mutex);
 	}
 	return ret;
 }
@@ -629,7 +624,7 @@ int destroy_session(int (*lprintf)(int level, const char* fmt, ...), CRYPT_SESSI
 	struct cert_list *psess = NULL;
 	int               ret = CRYPT_ERROR_NOTFOUND;
 
-	pthread_mutex_lock(&ssl_sess_list_mutex);
+	assert_pthread_mutex_lock(&ssl_sess_list_mutex);
 	sess = sess_list;
 	while (sess != NULL) {
 		if (sess->sess == csess) {
@@ -644,7 +639,7 @@ int destroy_session(int (*lprintf)(int level, const char* fmt, ...), CRYPT_SESSI
 		psess = sess;
 		sess = sess->next;
 	}
-	pthread_mutex_unlock(&ssl_sess_list_mutex);
+	assert_pthread_mutex_unlock(&ssl_sess_list_mutex);
 	if (sess != NULL) {
 		if (!rwlock_rdlock(&cert_epoch_lock)) {
 			lprintf(LOG_ERR, "Unable to unlock cert_epoch_lock for write at %d", __LINE__);
@@ -656,10 +651,10 @@ int destroy_session(int (*lprintf)(int level, const char* fmt, ...), CRYPT_SESSI
 				return CRYPT_ERROR_INTERNAL;
 			}
 			sess->sess = -1;
-			pthread_mutex_lock(&ssl_cert_list_mutex);
+			assert_pthread_mutex_lock(&ssl_cert_list_mutex);
 			sess->next = cert_list;
 			cert_list = sess;
-			pthread_mutex_unlock(&ssl_cert_list_mutex);
+			assert_pthread_mutex_unlock(&ssl_cert_list_mutex);
 			ret = cryptDestroySession(csess);
 		}
 		else {
diff --git a/src/syncterm/CHANGES b/src/syncterm/CHANGES
index 6b2afa0ef7be8fef8f61680e4c7937ba728d712a..6c0b1b4505f11b0c7c0aa014ed736ea32b8815c3 100644
--- a/src/syncterm/CHANGES
+++ b/src/syncterm/CHANGES
@@ -5,6 +5,8 @@ Fix blast-through in Mode 7 high ASCII mosaics
 Update Prestel/Mode 7 keybindings
 Don't disable status line for Atari ST modes
 Fix broken vertical (U+00A6) vs. vertical line (U+007C)
+Add custom palette support to list file
+Pass control key combinations in BBC Micro mode
 
 Version 1.6
 ------------